evophp 1.0.31 → 1.1.1
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 +106 -83
- package/bin/evophp.js +16 -0
- package/commands/dependencies.js +80 -0
- package/commands/help.js +71 -0
- package/commands/init.js +57 -0
- package/commands/install.js +91 -0
- package/commands/new.js +161 -0
- package/commands/uninstall.js +81 -0
- package/package.json +24 -17
- package/utils/config.js +20 -0
- package/utils/dependencies.js +33 -0
- package/utils/edit.js +42 -0
- package/utils/git.js +46 -0
- package/utils/github.js +49 -0
- package/utils/helpers.js +161 -0
- package/utils/logger.js +6 -0
- package/utils/validate.js +16 -0
- package/helpers.js +0 -101
- package/install-dependencies.js +0 -149
- package/install-plugin.js +0 -118
- package/installer.js +0 -223
- package/new-plugin.js +0 -298
- package/uninstall.js +0 -65
package/commands/new.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { readLineAsync, mergeConfig } = require("../utils/helpers");
|
|
4
|
+
const { getUserData, createGitHubRepo } = require("../utils/github");
|
|
5
|
+
const { cloneRepo } = require("../utils/git");
|
|
6
|
+
const { editFile, renameFile } = require("../utils/edit");
|
|
7
|
+
const { test } = require("../utils/validate");
|
|
8
|
+
const log = require("../utils/logger");
|
|
9
|
+
require("dotenv").config({ path: "./.env.development" });
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
command: "new <type> <name>",
|
|
13
|
+
aliases: ["n"],
|
|
14
|
+
describe: "Create a new plugin or theme from template",
|
|
15
|
+
|
|
16
|
+
builder: (yargs) =>
|
|
17
|
+
yargs
|
|
18
|
+
.positional("type", {
|
|
19
|
+
describe: "plugin or theme",
|
|
20
|
+
type: "string",
|
|
21
|
+
})
|
|
22
|
+
.positional("name", {
|
|
23
|
+
describe: "The plugin or theme name (without author)",
|
|
24
|
+
type: "string",
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
handler: async ({ type, name }) => {
|
|
28
|
+
try {
|
|
29
|
+
let gitHubToken = process.env.GITHUB_TOKEN;
|
|
30
|
+
|
|
31
|
+
if (!gitHubToken) {
|
|
32
|
+
gitHubToken = await readLineAsync(
|
|
33
|
+
"Enter GitHub Personal Access Token: "
|
|
34
|
+
);
|
|
35
|
+
const { saveToEnv } = require("../utils/helpers");
|
|
36
|
+
saveToEnv("GITHUB_TOKEN", gitHubToken);
|
|
37
|
+
log.success("GitHub token saved to .env.development");
|
|
38
|
+
}
|
|
39
|
+
type = type.toLowerCase();
|
|
40
|
+
|
|
41
|
+
if (!["plugin", "theme"].includes(type)) {
|
|
42
|
+
log.error("Invalid type. Must be plugin or theme.");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get authenticated username from token
|
|
47
|
+
const userData = await getUserData(gitHubToken);
|
|
48
|
+
const author = userData.login;
|
|
49
|
+
const pluginName = name;
|
|
50
|
+
|
|
51
|
+
log.info(`Authenticated as ${author}`);
|
|
52
|
+
log.info(`Creating ${type} "${pluginName}"...`);
|
|
53
|
+
|
|
54
|
+
if (type === "plugin") {
|
|
55
|
+
const entry = await setupPlugin(gitHubToken, author, pluginName);
|
|
56
|
+
log.success(`Plugin "${pluginName}" created with entry "${entry}"`);
|
|
57
|
+
} else {
|
|
58
|
+
await setupTheme(gitHubToken, author, pluginName);
|
|
59
|
+
log.success(`Theme "${pluginName}" created`);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
log.error(`Creation failed: ${err.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
async function setupPlugin(token, author, pluginName) {
|
|
69
|
+
const entry = await setupPluginRepo(token, author, pluginName);
|
|
70
|
+
const pluginPrefix = entry.replace("Main", "");
|
|
71
|
+
|
|
72
|
+
await editFile(`./Public/Modules/${pluginName}/Routes.php`, {
|
|
73
|
+
pluginName,
|
|
74
|
+
entry,
|
|
75
|
+
entryURI: entry.toLowerCase(),
|
|
76
|
+
pluginPrefix,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const controllerPath = `./Public/Modules/${pluginName}/Controller.php`;
|
|
80
|
+
if (fs.existsSync(controllerPath)) {
|
|
81
|
+
await editFile(controllerPath, { pluginName, pluginPrefix });
|
|
82
|
+
await renameFile(
|
|
83
|
+
controllerPath,
|
|
84
|
+
`./Public/Modules/${pluginName}/${pluginPrefix}Controller.php`
|
|
85
|
+
);
|
|
86
|
+
await renameFile(
|
|
87
|
+
`./Public/Modules/${pluginName}/Views/Entry`,
|
|
88
|
+
`./Public/Modules/${pluginName}/Views/${entry}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function setupPluginRepo(token, author, pluginName) {
|
|
96
|
+
await createGitHubRepo(token, "kinsokafor/EvoPlugin", pluginName);
|
|
97
|
+
await cloneRepo(
|
|
98
|
+
author,
|
|
99
|
+
pluginName,
|
|
100
|
+
path.join("Public", "Modules", pluginName)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
await createGitHubRepo(token, "kinsokafor/EvoPlugin.js", `${pluginName}.js`);
|
|
104
|
+
await cloneRepo(
|
|
105
|
+
author,
|
|
106
|
+
`${pluginName}.js`,
|
|
107
|
+
path.join("EvoJsDev", "Modules", pluginName)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const pluginPrefix = await getValidatedInput("Enter Plugin Prefix?", 3, true);
|
|
111
|
+
const entryURI = await getValidatedInput("Entry URI?", 20);
|
|
112
|
+
const entry = pluginPrefix + "Main";
|
|
113
|
+
|
|
114
|
+
await editFile(`./EvoJsDev/Modules/${pluginName}/config.json`, {
|
|
115
|
+
pluginName,
|
|
116
|
+
entry,
|
|
117
|
+
});
|
|
118
|
+
await renameFile(
|
|
119
|
+
`./EvoJsDev/Modules/${pluginName}/Entry`,
|
|
120
|
+
`./EvoJsDev/Modules/${pluginName}/${entry}`
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const configPath = `./EvoJsDev/Modules/${pluginName}/config.json`;
|
|
124
|
+
if (fs.existsSync(configPath)) {
|
|
125
|
+
const pluginConfig = require(path.resolve(configPath));
|
|
126
|
+
mergeConfig(pluginConfig);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return entry;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function setupTheme(token, author, pluginName) {
|
|
133
|
+
await createGitHubRepo(token, "kinsokafor/EvoTheme", pluginName);
|
|
134
|
+
await cloneRepo(
|
|
135
|
+
author,
|
|
136
|
+
pluginName,
|
|
137
|
+
path.join("Public", "Themes", pluginName)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
await editFile(`./Public/Themes/${pluginName}/Data/index.data.php`, {
|
|
141
|
+
pluginName,
|
|
142
|
+
});
|
|
143
|
+
await editFile(`./Public/Themes/${pluginName}/Data/login.data.php`, {
|
|
144
|
+
pluginName,
|
|
145
|
+
});
|
|
146
|
+
await editFile(`./Public/Themes/${pluginName}/Views/index.blade.php`, {
|
|
147
|
+
pluginName,
|
|
148
|
+
});
|
|
149
|
+
await editFile(`./Public/Themes/${pluginName}/Views/login.blade.php`, {
|
|
150
|
+
pluginName,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function getValidatedInput(label, maxLength, caps = false) {
|
|
155
|
+
let input = await readLineAsync(label);
|
|
156
|
+
while (!test(input, maxLength)) {
|
|
157
|
+
input = await readLineAsync(label);
|
|
158
|
+
}
|
|
159
|
+
input = input.trim().replaceAll(" ", "");
|
|
160
|
+
return caps ? input.toUpperCase() : input.toLowerCase();
|
|
161
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const rimraf = require("rimraf");
|
|
4
|
+
const editJsonFile = require("edit-json-file");
|
|
5
|
+
const { readLineAsync } = require("../utils/helpers");
|
|
6
|
+
const log = require("../utils/logger");
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
command: "uninstall [type] [name]",
|
|
10
|
+
aliases: ["u"],
|
|
11
|
+
describe: "Uninstall a plugin or theme",
|
|
12
|
+
|
|
13
|
+
builder: yargs =>
|
|
14
|
+
yargs
|
|
15
|
+
.positional("type", { describe: "plugin or theme", type: "string" })
|
|
16
|
+
.positional("name", { describe: "The plugin or theme name", type: "string" }),
|
|
17
|
+
|
|
18
|
+
handler: async ({ type, name }) => {
|
|
19
|
+
try {
|
|
20
|
+
// Prompt type if missing
|
|
21
|
+
if (!type || !["plugin", "theme"].includes(type.toLowerCase())) {
|
|
22
|
+
type = await readLineAsync("What are you uninstalling? (plugin/theme): ");
|
|
23
|
+
type = type.toLowerCase();
|
|
24
|
+
if (!["plugin", "theme"].includes(type)) {
|
|
25
|
+
log.error("Invalid type. Must be 'plugin' or 'theme'.");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Prompt name if missing
|
|
31
|
+
if (!name) {
|
|
32
|
+
name = await readLineAsync(`Enter the ${type} name (e.g., example${type}): `);
|
|
33
|
+
if (!name) {
|
|
34
|
+
log.error("You must provide a name.");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const paths =
|
|
40
|
+
type === "plugin"
|
|
41
|
+
? [path.join("Public", "Modules", name), path.join("EvoJsDev", "Modules", name)]
|
|
42
|
+
: [path.join("Public", "Themes", name)];
|
|
43
|
+
|
|
44
|
+
const existing = paths.filter(fs.existsSync);
|
|
45
|
+
|
|
46
|
+
if (existing.length === 0) {
|
|
47
|
+
log.warn(`No matching ${type} found for "${name}".`);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
log.info(`Preparing to uninstall ${type} "${name}" from:`);
|
|
52
|
+
existing.forEach(p => log.info(" - " + p));
|
|
53
|
+
|
|
54
|
+
const confirm = await readLineAsync("Are you sure you want to proceed? (y/n): ");
|
|
55
|
+
if (!["y", "yes"].includes(confirm.trim().toLowerCase())) {
|
|
56
|
+
log.warn("Uninstallation cancelled.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const dir of existing) {
|
|
61
|
+
rimraf.sync(dir);
|
|
62
|
+
log.success(`Deleted: ${dir}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (type === "plugin") {
|
|
66
|
+
const configPath = path.resolve("config.json");
|
|
67
|
+
if (fs.existsSync(configPath)) {
|
|
68
|
+
let file = editJsonFile(configPath);
|
|
69
|
+
file.unset(`modules.${name}`);
|
|
70
|
+
file.save();
|
|
71
|
+
log.success(`Removed '${name}' from config.json`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
log.success(`The ${type} "${name}" has been uninstalled successfully.`);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
log.error("Error during uninstall: " + err.message);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
package/package.json
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evophp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "installer.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "node installer.js"
|
|
8
|
-
},
|
|
3
|
+
"version": "1.1.01",
|
|
4
|
+
"description": "Command-line installer for the Evolution PHP framework",
|
|
9
5
|
"bin": {
|
|
10
|
-
"evophp": "
|
|
6
|
+
"evophp": "./bin/evophp.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "bin/evophp.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/evophp.js"
|
|
11
11
|
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"commands/",
|
|
15
|
+
"utils/"
|
|
16
|
+
],
|
|
12
17
|
"dependencies": {
|
|
13
18
|
"axios": "^1.7.9",
|
|
14
19
|
"dotenv": "^16.4.7",
|
|
15
20
|
"edit-json-file": "^1.8.0",
|
|
16
|
-
"rimraf": "^6.0.1"
|
|
21
|
+
"rimraf": "^6.0.1",
|
|
22
|
+
"yargs": "^17.7.2"
|
|
17
23
|
},
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
"keywords": [
|
|
25
|
+
"cli",
|
|
26
|
+
"evophp",
|
|
27
|
+
"installer",
|
|
28
|
+
"php-framework",
|
|
29
|
+
"evolution"
|
|
30
|
+
],
|
|
31
|
+
"author": "Kingsley Okafor",
|
|
32
|
+
"license": "MIT"
|
|
33
|
+
}
|
package/utils/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const configPath = path.join(require('os').homedir(), '.evophp-cli.json');
|
|
4
|
+
|
|
5
|
+
function loadConfig() {
|
|
6
|
+
if (fs.existsSync(configPath)) {
|
|
7
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
8
|
+
}
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function saveConfig(data) {
|
|
13
|
+
fs.writeFileSync(configPath, JSON.stringify(data, null, 2));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = {
|
|
17
|
+
loadConfig,
|
|
18
|
+
saveConfig,
|
|
19
|
+
configPath
|
|
20
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const log = require('./logger');
|
|
5
|
+
|
|
6
|
+
async function installDependencies(dir, type) {
|
|
7
|
+
const dependenciesFile = path.join(dir, 'dependencies.json');
|
|
8
|
+
if (!fs.existsSync(dependenciesFile)) {
|
|
9
|
+
log.info(`No ${type} dependencies.json found in ${dir}`);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const deps = JSON.parse(fs.readFileSync(dependenciesFile, 'utf-8'));
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
|
|
16
|
+
if (type === 'js') {
|
|
17
|
+
if (deps.prod) {
|
|
18
|
+
log.info("Installing JS production dependencies...");
|
|
19
|
+
execSync(`npm install ${deps.prod}`, { cwd, stdio: 'inherit' });
|
|
20
|
+
}
|
|
21
|
+
if (deps.dev) {
|
|
22
|
+
log.info("Installing JS development dependencies...");
|
|
23
|
+
execSync(`npm install ${deps.dev} --save-dev`, { cwd, stdio: 'inherit' });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (type === 'php' && deps.prod) {
|
|
28
|
+
log.info("Installing PHP dependencies...");
|
|
29
|
+
execSync(`composer require ${deps.prod}`, { cwd, stdio: 'inherit' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { installDependencies };
|
package/utils/edit.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const log = require("./logger");
|
|
4
|
+
|
|
5
|
+
async function editFile(file, data) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
fs.readFile(file, "utf8", (err, fileData) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
log.error(`Failed to read file ${file}`);
|
|
10
|
+
return reject(err);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (const key in data) {
|
|
14
|
+
fileData = fileData.replaceAll(`<${key}>`, data[key]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fs.writeFile(file, fileData, "utf8", err => {
|
|
18
|
+
if (err) {
|
|
19
|
+
log.error(`Failed to write file ${file}`);
|
|
20
|
+
return reject(err);
|
|
21
|
+
}
|
|
22
|
+
log.success(`${file} updated.`);
|
|
23
|
+
resolve(file);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function renameFile(oldPath, newPath) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
fs.rename(oldPath, newPath, err => {
|
|
32
|
+
if (err) {
|
|
33
|
+
log.error(`Failed to rename ${oldPath}`);
|
|
34
|
+
return reject(err);
|
|
35
|
+
}
|
|
36
|
+
log.success(`Renamed: ${path.basename(oldPath)} → ${path.basename(newPath)}`);
|
|
37
|
+
resolve();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { editFile, renameFile };
|
package/utils/git.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const { execSync } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const rimraf = require("rimraf");
|
|
5
|
+
const log = require("./logger");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Clone a GitHub repo into a destination folder.
|
|
9
|
+
* Supports:
|
|
10
|
+
* cloneRepo("author", "repo", "dest")
|
|
11
|
+
* cloneRepo("author/repo", null, "dest")
|
|
12
|
+
*
|
|
13
|
+
* @param {string} authorOrFull - GitHub username OR "author/repo"
|
|
14
|
+
* @param {string|null} repo - Optional repo name if not included in first arg
|
|
15
|
+
* @param {string} dest - Local destination path
|
|
16
|
+
*/
|
|
17
|
+
async function cloneRepo(authorOrFull, repo, dest) {
|
|
18
|
+
let author = "";
|
|
19
|
+
let repoName = "";
|
|
20
|
+
|
|
21
|
+
if (repo === null || repo === undefined) {
|
|
22
|
+
// authorOrFull is in "author/repo" format
|
|
23
|
+
const parts = authorOrFull.split("/");
|
|
24
|
+
if (parts.length !== 2) {
|
|
25
|
+
log.error("Invalid GitHub repo format. Use 'author/repo'.");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
[author, repoName] = parts;
|
|
29
|
+
} else {
|
|
30
|
+
// Passed separately
|
|
31
|
+
author = authorOrFull;
|
|
32
|
+
repoName = repo;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const cloneUrl = `https://github.com/${author}/${repoName}.git`;
|
|
36
|
+
|
|
37
|
+
if (fs.existsSync(dest)) {
|
|
38
|
+
log.warn(`Deleting existing folder: ${dest}`);
|
|
39
|
+
rimraf.sync(dest);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
log.info(`Cloning ${author}/${repoName} → ${dest}`);
|
|
43
|
+
execSync(`git clone ${cloneUrl} ${dest}`, { stdio: "inherit" });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { cloneRepo };
|
package/utils/github.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// utils/github.js
|
|
2
|
+
const axios = require("axios");
|
|
3
|
+
const log = require("./logger");
|
|
4
|
+
|
|
5
|
+
async function getUserData(token) {
|
|
6
|
+
try {
|
|
7
|
+
const res = await axios.get("https://api.github.com/user", {
|
|
8
|
+
headers: {
|
|
9
|
+
Authorization: `token ${token}`,
|
|
10
|
+
Accept: "application/vnd.github.v3+json",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
return res.data;
|
|
14
|
+
} catch (e) {
|
|
15
|
+
log.error("Failed to fetch GitHub user info.");
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function createGitHubRepo(token, templateRepo, pluginName) {
|
|
21
|
+
try {
|
|
22
|
+
const res = await axios.post(
|
|
23
|
+
`https://api.github.com/repos/${templateRepo}/generate`,
|
|
24
|
+
{
|
|
25
|
+
name: pluginName,
|
|
26
|
+
private: true,
|
|
27
|
+
description: `A new ${templateRepo.split("/")[1]} created from template.`,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: `token ${token}`,
|
|
32
|
+
Accept: "application/vnd.github.v3+json",
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (res.status === 201) {
|
|
38
|
+
log.success(`Created repo ${pluginName} from ${templateRepo}`);
|
|
39
|
+
} else {
|
|
40
|
+
log.error("Failed to create repo:", res.data);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
log.error(`GitHub repo creation failed: ${e.message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { getUserData, createGitHubRepo };
|
package/utils/helpers.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const readline = require("readline");
|
|
2
|
+
const editJsonFile = require("edit-json-file");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Prompt user for input with a message
|
|
6
|
+
* @param {string} msg - The prompt message to display
|
|
7
|
+
* @returns {Promise<string>}
|
|
8
|
+
*/
|
|
9
|
+
const readLineAsync = (msg) => {
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
rl.question(msg, (userRes) => {
|
|
17
|
+
resolve(userRes.trim());
|
|
18
|
+
rl.close();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate and split a name into [author, pluginName]
|
|
25
|
+
* Accepts "author/plugin" or a GitHub URL
|
|
26
|
+
* @param {string} name
|
|
27
|
+
* @returns {Promise<[string, string]>}
|
|
28
|
+
*/
|
|
29
|
+
const splitPluginName = async (name) => {
|
|
30
|
+
const isValid = (input) =>
|
|
31
|
+
/^[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(input);
|
|
32
|
+
|
|
33
|
+
while (true) {
|
|
34
|
+
name = name.replace(/^(https?:\/\/)?(www\.)?github\.com\//, "").trim();
|
|
35
|
+
if (isValid(name)) return name.split("/");
|
|
36
|
+
|
|
37
|
+
console.log("Format must be: author/plugin-name");
|
|
38
|
+
name = await readLineAsync("Enter the plugin name (or 'cancel' to exit): ");
|
|
39
|
+
if (name.toLowerCase() === "cancel") {
|
|
40
|
+
console.log("Process canceled by user.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if value is a plain object
|
|
48
|
+
*/
|
|
49
|
+
const isObject = (item) =>
|
|
50
|
+
item && typeof item === "object" && !Array.isArray(item);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Deep merge multiple objects into one
|
|
54
|
+
*/
|
|
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
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return mergeDeep(target, ...sources);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Merge plugin config into config.json
|
|
75
|
+
*/
|
|
76
|
+
const mergeConfig = async (config) => {
|
|
77
|
+
const file = editJsonFile("./config.json");
|
|
78
|
+
|
|
79
|
+
for (const key in config) {
|
|
80
|
+
const existing = file.get(key);
|
|
81
|
+
if (existing === undefined) {
|
|
82
|
+
file.set(key, config[key]);
|
|
83
|
+
} else if (isObject(existing)) {
|
|
84
|
+
const merged = await mergeDeep(existing, config[key]);
|
|
85
|
+
file.set(key, merged);
|
|
86
|
+
} else {
|
|
87
|
+
file.set(key, config[key]);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
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
|
+
|
|
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);
|
|
118
|
+
}
|
|
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");
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Save a key-value pair to .env.development (creates or updates)
|
|
130
|
+
* @param {string} key
|
|
131
|
+
* @param {string} value
|
|
132
|
+
*/
|
|
133
|
+
function saveToEnv(key, value) {
|
|
134
|
+
const envPath = path.resolve(".env.development");
|
|
135
|
+
|
|
136
|
+
let env = "";
|
|
137
|
+
if (fs.existsSync(envPath)) {
|
|
138
|
+
env = fs.readFileSync(envPath, "utf-8");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
142
|
+
const line = `${key}=${value.trim()}`;
|
|
143
|
+
|
|
144
|
+
if (regex.test(env)) {
|
|
145
|
+
// Update existing key
|
|
146
|
+
env = env.replace(regex, line);
|
|
147
|
+
} else {
|
|
148
|
+
// Append new key
|
|
149
|
+
env += (env.endsWith("\n") ? "" : "\n") + line + "\n";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fs.writeFileSync(envPath, env, "utf-8");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
readLineAsync,
|
|
157
|
+
splitPluginName,
|
|
158
|
+
mergeConfig,
|
|
159
|
+
addPluginEntry,
|
|
160
|
+
saveToEnv
|
|
161
|
+
};
|
package/utils/logger.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const log = require("./logger");
|
|
2
|
+
|
|
3
|
+
function test(str, maxLength) {
|
|
4
|
+
str = str.trim().replaceAll(" ", "");
|
|
5
|
+
if (str.length > maxLength) {
|
|
6
|
+
log.error(`Input can't exceed ${maxLength} characters.`);
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
if (str.length < 1) {
|
|
10
|
+
log.error(`Input is empty.`);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return str;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { test };
|