@exilonstudios/cli 1.0.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/LICENSE +21 -0
- package/README.md +342 -0
- package/dist/cli.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1275 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { intro, outro } from "@clack/prompts";
|
|
6
|
+
import chalk6 from "chalk";
|
|
7
|
+
import gradient from "gradient-string";
|
|
8
|
+
|
|
9
|
+
// src/commands/new.ts
|
|
10
|
+
import { select, text, confirm, multiselect, cancel } from "@clack/prompts";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
import fs from "fs-extra";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { execa } from "execa";
|
|
16
|
+
import { Listr } from "listr2";
|
|
17
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
var __dirname = path.dirname(__filename);
|
|
19
|
+
var PLUGIN_STARTERS = [
|
|
20
|
+
{ value: "basic", label: "\u{1F4E6} Basic Plugin", hint: "Minimal plugin structure" },
|
|
21
|
+
{ value: "blog", label: "\u{1F4DD} Blog Extension", hint: "Extend blog functionality" },
|
|
22
|
+
{ value: "shop", label: "\u{1F6D2} Shop Integration", hint: "E-commerce integration" },
|
|
23
|
+
{ value: "analytics", label: "\u{1F4CA} Analytics Provider", hint: "Custom analytics tracking" },
|
|
24
|
+
{ value: "notification", label: "\u{1F514} Notification Channel", hint: "Custom notification channel" }
|
|
25
|
+
];
|
|
26
|
+
var THEME_STARTERS = [
|
|
27
|
+
{ value: "minimal", label: "\u{1F3A8} Minimal Theme", hint: "Clean, minimal design" },
|
|
28
|
+
{ value: "blog", label: "\u{1F4F0} Blog Theme", hint: "Content-focused blog theme" },
|
|
29
|
+
{ value: "gaming", label: "\u{1F3AE} Gaming Theme", hint: "Gaming server theme" },
|
|
30
|
+
{ value: "portfolio", label: "\u{1F4BC} Portfolio Theme", hint: "Personal portfolio theme" }
|
|
31
|
+
];
|
|
32
|
+
var PLUGIN_FEATURES = [
|
|
33
|
+
{ value: "admin", label: "\u{1F527} Admin Panel", hint: "Backend admin interface" },
|
|
34
|
+
{ value: "public", label: "\u{1F310} Public Pages", hint: "Frontend user-facing pages" },
|
|
35
|
+
{ value: "api", label: "\u{1F50C} API Routes", hint: "REST API endpoints" },
|
|
36
|
+
{ value: "models", label: "\u{1F4CA} Database Models", hint: "Eloquent models" },
|
|
37
|
+
{ value: "middleware", label: "\u{1F6E1}\uFE0F Middleware", hint: "Custom middleware" },
|
|
38
|
+
{ value: "commands", label: "\u2328\uFE0F Artisan Commands", hint: "Console commands" },
|
|
39
|
+
{ value: "react", label: "\u269B\uFE0F React Components", hint: "React + Inertia pages" },
|
|
40
|
+
{ value: "settings", label: "\u2699\uFE0F Settings Page", hint: "Configurable settings" }
|
|
41
|
+
];
|
|
42
|
+
var THEME_FEATURES = [
|
|
43
|
+
{ value: "templates", label: "\u{1F4C4} Blade Templates", hint: "Blade view templates" },
|
|
44
|
+
{ value: "assets", label: "\u{1F3A8} CSS/JS Assets", hint: "Frontend assets" },
|
|
45
|
+
{ value: "config", label: "\u2699\uFE0F Theme Config", hint: "Customizable theme options" },
|
|
46
|
+
{ value: "widgets", label: "\u{1F9E9} Widget Areas", hint: "Dynamic widget areas" },
|
|
47
|
+
{ value: "colors", label: "\u{1F3A8} Color Schemes", hint: "Multiple color schemes" }
|
|
48
|
+
];
|
|
49
|
+
async function newCommand(typeArg) {
|
|
50
|
+
try {
|
|
51
|
+
let type;
|
|
52
|
+
if (typeArg && ["plugin", "theme"].includes(typeArg)) {
|
|
53
|
+
type = typeArg;
|
|
54
|
+
} else {
|
|
55
|
+
const typeSelect = await select({
|
|
56
|
+
message: "What do you want to create?",
|
|
57
|
+
options: [
|
|
58
|
+
{ value: "plugin", label: "\u{1F50C} Plugin", hint: "Extend CMS functionality" },
|
|
59
|
+
{ value: "theme", label: "\u{1F3A8} Theme", hint: "Customize the appearance" }
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
if (typeof typeSelect !== "string") {
|
|
63
|
+
cancel("Operation cancelled");
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
type = typeSelect;
|
|
67
|
+
}
|
|
68
|
+
const name = await text({
|
|
69
|
+
message: `Project name (e.g., ${type === "plugin" ? "my-awesome-plugin" : "my-awesome-theme"}):`,
|
|
70
|
+
placeholder: type === "plugin" ? "my-awesome-plugin" : "my-awesome-theme",
|
|
71
|
+
validate: (value) => {
|
|
72
|
+
if (!value) return "Name is required";
|
|
73
|
+
if (!/^[a-z0-9-]+$/.test(value)) return "Use only lowercase letters, numbers, and hyphens";
|
|
74
|
+
if (value.length < 3) return "Name must be at least 3 characters";
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
if (typeof name !== "string") {
|
|
78
|
+
cancel("Operation cancelled");
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
const description = await text({
|
|
82
|
+
message: "Description:",
|
|
83
|
+
placeholder: `An amazing ${type} for ExilonCMS`
|
|
84
|
+
});
|
|
85
|
+
if (typeof description !== "string") {
|
|
86
|
+
cancel("Operation cancelled");
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
const author = await text({
|
|
90
|
+
message: "Author name:",
|
|
91
|
+
placeholder: "Your Name"
|
|
92
|
+
});
|
|
93
|
+
if (typeof author !== "string") {
|
|
94
|
+
cancel("Operation cancelled");
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
let namespace = "ExilonCMS\\Plugins\\" + toPascalCase(name);
|
|
98
|
+
if (type === "plugin") {
|
|
99
|
+
const namespaceInput = await text({
|
|
100
|
+
message: "PHP Namespace:",
|
|
101
|
+
placeholder: namespace,
|
|
102
|
+
initialValue: namespace
|
|
103
|
+
});
|
|
104
|
+
if (typeof namespaceInput !== "string") {
|
|
105
|
+
cancel("Operation cancelled");
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
namespace = namespaceInput;
|
|
109
|
+
}
|
|
110
|
+
const useStarter = await confirm({
|
|
111
|
+
message: `Use a starter ${type}?`,
|
|
112
|
+
initialValue: true
|
|
113
|
+
});
|
|
114
|
+
if (typeof useStarter !== "boolean") {
|
|
115
|
+
cancel("Operation cancelled");
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
let starter;
|
|
119
|
+
if (useStarter) {
|
|
120
|
+
const starters = type === "plugin" ? PLUGIN_STARTERS : THEME_STARTERS;
|
|
121
|
+
const starterSelect = await select({
|
|
122
|
+
message: `Choose a ${type} starter:`,
|
|
123
|
+
options: starters
|
|
124
|
+
});
|
|
125
|
+
if (typeof starterSelect !== "string") {
|
|
126
|
+
cancel("Operation cancelled");
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
starter = starterSelect;
|
|
130
|
+
}
|
|
131
|
+
const features = type === "plugin" ? PLUGIN_FEATURES : THEME_FEATURES;
|
|
132
|
+
const selectedFeatures = await multiselect({
|
|
133
|
+
message: `Select features (press space to select, enter to continue):`,
|
|
134
|
+
options: features,
|
|
135
|
+
required: false
|
|
136
|
+
});
|
|
137
|
+
if (!Array.isArray(selectedFeatures)) {
|
|
138
|
+
cancel("Operation cancelled");
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
const options = {
|
|
142
|
+
type,
|
|
143
|
+
name,
|
|
144
|
+
description: description || `An amazing ${type} for ExilonCMS`,
|
|
145
|
+
author: author || "Unknown",
|
|
146
|
+
version: "1.0.0",
|
|
147
|
+
license: "MIT",
|
|
148
|
+
useStarter,
|
|
149
|
+
starter,
|
|
150
|
+
features: selectedFeatures,
|
|
151
|
+
namespace
|
|
152
|
+
};
|
|
153
|
+
const shouldContinue = await confirm({
|
|
154
|
+
message: "Ready to create?",
|
|
155
|
+
initialValue: true
|
|
156
|
+
});
|
|
157
|
+
if (typeof shouldContinue !== "boolean" || !shouldContinue) {
|
|
158
|
+
cancel("Operation cancelled");
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
await createProject(options);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error instanceof Error && error.message !== "Operation cancelled") {
|
|
164
|
+
console.error(chalk.red("Error:"), error.message);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function createProject(options) {
|
|
169
|
+
const tasks = new Listr([
|
|
170
|
+
{
|
|
171
|
+
title: "Creating project structure",
|
|
172
|
+
task: async () => {
|
|
173
|
+
const projectPath = path.join(process.cwd(), options.name);
|
|
174
|
+
await fs.ensureDir(projectPath);
|
|
175
|
+
if (options.type === "plugin") {
|
|
176
|
+
await createPluginStructure(projectPath, options);
|
|
177
|
+
} else {
|
|
178
|
+
await createThemeStructure(projectPath, options);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
title: "Generating configuration files",
|
|
184
|
+
task: async () => {
|
|
185
|
+
const projectPath = path.join(process.cwd(), options.name);
|
|
186
|
+
await generateConfigFiles(projectPath, options);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
title: "Creating source files",
|
|
191
|
+
task: async () => {
|
|
192
|
+
const projectPath = path.join(process.cwd(), options.name);
|
|
193
|
+
await generateSourceFiles(projectPath, options);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
title: "Installing dependencies",
|
|
198
|
+
task: async () => {
|
|
199
|
+
const projectPath = path.join(process.cwd(), options.name);
|
|
200
|
+
if (options.type === "theme" && !options.features.includes("react")) {
|
|
201
|
+
return "Skipped (no npm dependencies)";
|
|
202
|
+
}
|
|
203
|
+
await execa("npm", ["install"], { cwd: projectPath });
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title: "Initializing git repository",
|
|
208
|
+
task: async () => {
|
|
209
|
+
const projectPath = path.join(process.cwd(), options.name);
|
|
210
|
+
await execa("git", ["init"], { cwd: projectPath });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
]);
|
|
214
|
+
await tasks.run();
|
|
215
|
+
}
|
|
216
|
+
async function createPluginStructure(projectPath, options) {
|
|
217
|
+
const dirs = [
|
|
218
|
+
"src",
|
|
219
|
+
"src/Models",
|
|
220
|
+
"src/Http/Controllers",
|
|
221
|
+
"src/Http/Middleware",
|
|
222
|
+
"src/Http/Requests",
|
|
223
|
+
"database/migrations",
|
|
224
|
+
"routes",
|
|
225
|
+
"resources/views",
|
|
226
|
+
"resources/js",
|
|
227
|
+
"resources/css",
|
|
228
|
+
"config",
|
|
229
|
+
"tests"
|
|
230
|
+
];
|
|
231
|
+
for (const dir of dirs) {
|
|
232
|
+
await fs.ensureDir(path.join(projectPath, dir));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function createThemeStructure(projectPath, options) {
|
|
236
|
+
const dirs = [
|
|
237
|
+
"resources/views",
|
|
238
|
+
"resources/views/layouts",
|
|
239
|
+
"resources/views/components",
|
|
240
|
+
"resources/css",
|
|
241
|
+
"resources/js",
|
|
242
|
+
"config"
|
|
243
|
+
];
|
|
244
|
+
for (const dir of dirs) {
|
|
245
|
+
await fs.ensureDir(path.join(projectPath, dir));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function generateConfigFiles(projectPath, options) {
|
|
249
|
+
if (options.type === "plugin") {
|
|
250
|
+
const pluginJson = {
|
|
251
|
+
name: options.name,
|
|
252
|
+
version: options.version,
|
|
253
|
+
description: options.description,
|
|
254
|
+
author: options.author,
|
|
255
|
+
license: options.license,
|
|
256
|
+
type: "plugin",
|
|
257
|
+
namespace: options.namespace,
|
|
258
|
+
service_provider: `${options.namespace}\\${toPascalCase(options.name)}ServiceProvider`,
|
|
259
|
+
autoload: {
|
|
260
|
+
"psr-4": {
|
|
261
|
+
[options.namespace + "\\"]: "src/"
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
extras: {
|
|
265
|
+
"laravel": {
|
|
266
|
+
"providers": [
|
|
267
|
+
`${options.namespace}\\${toPascalCase(options.name)}ServiceProvider`
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
await fs.writeJson(path.join(projectPath, "plugin.json"), pluginJson, { spaces: 2 });
|
|
273
|
+
}
|
|
274
|
+
if (options.type === "theme") {
|
|
275
|
+
const themeJson = {
|
|
276
|
+
name: options.name,
|
|
277
|
+
version: options.version,
|
|
278
|
+
description: options.description,
|
|
279
|
+
author: options.author,
|
|
280
|
+
license: options.license,
|
|
281
|
+
type: "theme",
|
|
282
|
+
screenshot: "screenshot.png",
|
|
283
|
+
supports: {
|
|
284
|
+
"exiloncms": "^1.0.0"
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
await fs.writeJson(path.join(projectPath, "theme.json"), themeJson, { spaces: 2 });
|
|
288
|
+
}
|
|
289
|
+
const packageJson = {
|
|
290
|
+
name: `exiloncms-${options.type}-${options.name}`,
|
|
291
|
+
version: options.version,
|
|
292
|
+
description: options.description,
|
|
293
|
+
author: options.author,
|
|
294
|
+
license: options.license,
|
|
295
|
+
private: true,
|
|
296
|
+
scripts: {
|
|
297
|
+
dev: "vite",
|
|
298
|
+
build: "vite build",
|
|
299
|
+
preview: "vite preview"
|
|
300
|
+
},
|
|
301
|
+
dependencies: {},
|
|
302
|
+
devDependencies: {
|
|
303
|
+
vite: "^5.0.0",
|
|
304
|
+
"@vitejs/plugin-react": "^4.2.0"
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
if (options.features.includes("react")) {
|
|
308
|
+
packageJson.dependencies = {
|
|
309
|
+
"react": "^18.2.0",
|
|
310
|
+
"react-dom": "^18.2.0",
|
|
311
|
+
"@inertiajs/react": "^1.0.0"
|
|
312
|
+
};
|
|
313
|
+
packageJson.devDependencies = {
|
|
314
|
+
...packageJson.devDependencies,
|
|
315
|
+
"@types/react": "^18.2.0",
|
|
316
|
+
"@types/react-dom": "^18.2.0",
|
|
317
|
+
"typescript": "^5.0.0"
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
await fs.writeJson(path.join(projectPath, "package.json"), packageJson, { spaces: 2 });
|
|
321
|
+
if (options.type === "plugin") {
|
|
322
|
+
const composerJson = {
|
|
323
|
+
name: `exiloncms/${options.name}`,
|
|
324
|
+
description: options.description,
|
|
325
|
+
type: "exiloncms-plugin",
|
|
326
|
+
version: options.version,
|
|
327
|
+
license: options.license,
|
|
328
|
+
authors: [
|
|
329
|
+
{ name: options.author }
|
|
330
|
+
],
|
|
331
|
+
require: {
|
|
332
|
+
"php": "^8.2",
|
|
333
|
+
"laravel/framework": "^11.0"
|
|
334
|
+
},
|
|
335
|
+
autoload: {
|
|
336
|
+
"psr-4": {
|
|
337
|
+
[options.namespace + "\\"]: "src/"
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
"extra": {
|
|
341
|
+
"exiloncms": {
|
|
342
|
+
"name": options.name,
|
|
343
|
+
"namespace": options.namespace
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
minimumStability: "dev",
|
|
347
|
+
preferStable: true
|
|
348
|
+
};
|
|
349
|
+
await fs.writeJson(path.join(projectPath, "composer.json"), composerJson, { spaces: 2 });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
async function generateSourceFiles(projectPath, options) {
|
|
353
|
+
if (options.type === "plugin") {
|
|
354
|
+
await generatePluginServiceProvider(projectPath, options);
|
|
355
|
+
await generatePluginRoutes(projectPath, options);
|
|
356
|
+
} else {
|
|
357
|
+
await generateThemeFiles(projectPath, options);
|
|
358
|
+
}
|
|
359
|
+
await generateReadme(projectPath, options);
|
|
360
|
+
await generateLicense(projectPath, options);
|
|
361
|
+
await generateGitignore(projectPath, options);
|
|
362
|
+
}
|
|
363
|
+
async function generatePluginServiceProvider(projectPath, options) {
|
|
364
|
+
const className = toPascalCase(options.name) + "ServiceProvider";
|
|
365
|
+
const content = `<?php
|
|
366
|
+
|
|
367
|
+
namespace ${options.namespace};
|
|
368
|
+
|
|
369
|
+
use Illuminate\\Support\\ServiceProvider;
|
|
370
|
+
use Illuminate\\Support\\Facades\\Route;
|
|
371
|
+
|
|
372
|
+
class ${className} extends ServiceProvider
|
|
373
|
+
{
|
|
374
|
+
/**
|
|
375
|
+
* Bootstrap any package services.
|
|
376
|
+
*/
|
|
377
|
+
public function boot(): void
|
|
378
|
+
{
|
|
379
|
+
// Load routes
|
|
380
|
+
$this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
|
|
381
|
+
$this->loadRoutesFrom(__DIR__ . '/../routes/admin.php');
|
|
382
|
+
|
|
383
|
+
// Load views
|
|
384
|
+
$this->loadViewsFrom(__DIR__ . '/../resources/views', '${options.name}');
|
|
385
|
+
|
|
386
|
+
// Load migrations
|
|
387
|
+
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
|
|
388
|
+
|
|
389
|
+
// Publish assets
|
|
390
|
+
$this->publishes([
|
|
391
|
+
__DIR__ . '/../resources/js' => public_path('vendor/${options.name}'),
|
|
392
|
+
], ['${options.name}', '${options.name}-assets']);
|
|
393
|
+
|
|
394
|
+
// Publish config
|
|
395
|
+
if ($this->app->runningInConsole()) {
|
|
396
|
+
$this->publishes([
|
|
397
|
+
__DIR__ . '/../config/${options.name}.php' => config_path('${options.name}.php'),
|
|
398
|
+
], '${options.name}-config');
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Register any package services.
|
|
404
|
+
*/
|
|
405
|
+
public function register(): void
|
|
406
|
+
{
|
|
407
|
+
// Merge config
|
|
408
|
+
$this->mergeConfigFrom(
|
|
409
|
+
__DIR__ . '/../config/${options.name}.php',
|
|
410
|
+
'${options.name}'
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
`;
|
|
415
|
+
await fs.writeFile(path.join(projectPath, "src", className + ".php"), content);
|
|
416
|
+
}
|
|
417
|
+
async function generatePluginRoutes(projectPath, options) {
|
|
418
|
+
const webRoutes = `<?php
|
|
419
|
+
|
|
420
|
+
use Illuminate\\Support\\Facades\\Route;
|
|
421
|
+
use ${options.namespace}\\Http\\Controllers\\ExampleController;
|
|
422
|
+
|
|
423
|
+
// Public routes
|
|
424
|
+
Route::prefix('${options.name}')->group(function () {
|
|
425
|
+
Route::get('/', [ExampleController::class, 'index'])->name('${options.name}.index');
|
|
426
|
+
});
|
|
427
|
+
`;
|
|
428
|
+
await fs.writeFile(path.join(projectPath, "routes", "web.php"), webRoutes);
|
|
429
|
+
const adminRoutes = `<?php
|
|
430
|
+
|
|
431
|
+
use Illuminate\\Support\\Facades\\Route;
|
|
432
|
+
use ${options.namespace}\\Http\\Controllers\\Admin\\ExampleController;
|
|
433
|
+
|
|
434
|
+
// Admin routes
|
|
435
|
+
Route::prefix('admin/${options.name}')
|
|
436
|
+
->middleware(['web', 'auth', 'admin'])
|
|
437
|
+
->group(function () {
|
|
438
|
+
Route::get('/', [ExampleController::class, 'index'])->name('admin.${options.name}.index');
|
|
439
|
+
});
|
|
440
|
+
`;
|
|
441
|
+
await fs.writeFile(path.join(projectPath, "routes", "admin.php"), adminRoutes);
|
|
442
|
+
}
|
|
443
|
+
async function generateThemeFiles(projectPath, options) {
|
|
444
|
+
const layout = `<!DOCTYPE html>
|
|
445
|
+
<html lang="{{ app()->getLocale() }}">
|
|
446
|
+
<head>
|
|
447
|
+
<meta charset="utf-8">
|
|
448
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
449
|
+
<title>@yield('title') - {{ setting('name') }}</title>
|
|
450
|
+
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
451
|
+
</head>
|
|
452
|
+
<body class="antialiased">
|
|
453
|
+
@yield('content')
|
|
454
|
+
|
|
455
|
+
@stack('scripts')
|
|
456
|
+
</body>
|
|
457
|
+
</html>
|
|
458
|
+
`;
|
|
459
|
+
await fs.writeFile(path.join(projectPath, "resources/views/layouts/app.blade.php"), layout);
|
|
460
|
+
const home = `@extends('layouts.app')
|
|
461
|
+
|
|
462
|
+
@section('title', 'Home')
|
|
463
|
+
|
|
464
|
+
@section('content')
|
|
465
|
+
<div class="container mx-auto px-4 py-8">
|
|
466
|
+
<h1 class="text-4xl font-bold">Welcome to {{ $page->title ?? theme('name') }}</h1>
|
|
467
|
+
<p class="mt-4 text-gray-600">
|
|
468
|
+
{{ $page->content ?? 'This is the ' . theme('name') . ' theme.' }}
|
|
469
|
+
</p>
|
|
470
|
+
</div>
|
|
471
|
+
@endsection
|
|
472
|
+
`;
|
|
473
|
+
await fs.writeFile(path.join(projectPath, "resources/views/home.blade.php"), home);
|
|
474
|
+
}
|
|
475
|
+
async function generateReadme(projectPath, options) {
|
|
476
|
+
const content = `# ${toPascalCase(options.name)}
|
|
477
|
+
|
|
478
|
+
${options.description}
|
|
479
|
+
|
|
480
|
+
## Author
|
|
481
|
+
${options.author}
|
|
482
|
+
|
|
483
|
+
## Version
|
|
484
|
+
${options.version}
|
|
485
|
+
|
|
486
|
+
## License
|
|
487
|
+
${options.license}
|
|
488
|
+
|
|
489
|
+
## Installation
|
|
490
|
+
|
|
491
|
+
### For Plugins
|
|
492
|
+
\`\`\`bash
|
|
493
|
+
composer require exiloncms/${options.name}
|
|
494
|
+
php artisan vendor:publish --tag="${options.name}"
|
|
495
|
+
php artisan migrate
|
|
496
|
+
\`\`\`
|
|
497
|
+
|
|
498
|
+
### For Themes
|
|
499
|
+
\`\`\`bash
|
|
500
|
+
# Copy to themes directory
|
|
501
|
+
cp -r ${options.name} /path/to/cms/themes/${options.name}
|
|
502
|
+
|
|
503
|
+
# Or use the CLI
|
|
504
|
+
exiloncms install ${options.name}
|
|
505
|
+
\`\`\`
|
|
506
|
+
|
|
507
|
+
## Usage
|
|
508
|
+
|
|
509
|
+
${options.type === "plugin" ? '### Configuration\n\nPublish the config file:\n```bash\nphp artisan vendor:publish --tag="${options.name}-config"\n```' : "### Screenshot\n\n"}
|
|
510
|
+
|
|
511
|
+
## Features
|
|
512
|
+
|
|
513
|
+
${options.features.map((f) => `- ${f}`).join("\n")}
|
|
514
|
+
|
|
515
|
+
## Development
|
|
516
|
+
|
|
517
|
+
\`\`\`bash
|
|
518
|
+
# Install dependencies
|
|
519
|
+
npm install
|
|
520
|
+
|
|
521
|
+
# Build assets
|
|
522
|
+
npm run build
|
|
523
|
+
|
|
524
|
+
# Watch for changes
|
|
525
|
+
npm run dev
|
|
526
|
+
\`\`\`
|
|
527
|
+
|
|
528
|
+
## Support
|
|
529
|
+
|
|
530
|
+
For issues and feature requests, please create an issue on the repository.
|
|
531
|
+
`;
|
|
532
|
+
await fs.writeFile(path.join(projectPath, "README.md"), content);
|
|
533
|
+
}
|
|
534
|
+
async function generateLicense(projectPath, options) {
|
|
535
|
+
const year = (/* @__PURE__ */ new Date()).getFullYear();
|
|
536
|
+
const content = `MIT License
|
|
537
|
+
|
|
538
|
+
Copyright (c) ${year} ${options.author}
|
|
539
|
+
|
|
540
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
541
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
542
|
+
in the Software without restriction, including without limitation the rights
|
|
543
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
544
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
545
|
+
furnished to do so, subject to the following conditions:
|
|
546
|
+
|
|
547
|
+
The above copyright notice and this permission notice shall be included in all
|
|
548
|
+
copies or substantial portions of the Software.
|
|
549
|
+
|
|
550
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
551
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
552
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
553
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
554
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
555
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
556
|
+
SOFTWARE.
|
|
557
|
+
`;
|
|
558
|
+
await fs.writeFile(path.join(projectPath, "LICENSE"), content);
|
|
559
|
+
}
|
|
560
|
+
async function generateGitignore(projectPath, options) {
|
|
561
|
+
const content = `node_modules/
|
|
562
|
+
dist/
|
|
563
|
+
build/
|
|
564
|
+
vendor/
|
|
565
|
+
.env
|
|
566
|
+
.env.local
|
|
567
|
+
.DS_Store
|
|
568
|
+
*.log
|
|
569
|
+
coverage/
|
|
570
|
+
.nyc_output/
|
|
571
|
+
.cache/
|
|
572
|
+
`;
|
|
573
|
+
await fs.writeFile(path.join(projectPath, ".gitignore"), content);
|
|
574
|
+
}
|
|
575
|
+
function toPascalCase(str) {
|
|
576
|
+
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/commands/install.ts
|
|
580
|
+
import { select as select2, text as text2, confirm as confirm2, cancel as cancel2 } from "@clack/prompts";
|
|
581
|
+
import chalk2 from "chalk";
|
|
582
|
+
import fs2 from "fs-extra";
|
|
583
|
+
import path2 from "path";
|
|
584
|
+
import { execa as execa2 } from "execa";
|
|
585
|
+
import axios from "axios";
|
|
586
|
+
import { Listr as Listr2 } from "listr2";
|
|
587
|
+
var MARKETPLACE_API = "https://marketplace.exiloncms.fr/api/v1";
|
|
588
|
+
async function installCommand(nameArg) {
|
|
589
|
+
try {
|
|
590
|
+
let packageName;
|
|
591
|
+
if (nameArg) {
|
|
592
|
+
packageName = nameArg;
|
|
593
|
+
} else {
|
|
594
|
+
const searchTerm = await text2({
|
|
595
|
+
message: "Enter package name or search term:",
|
|
596
|
+
placeholder: "e.g., blog"
|
|
597
|
+
});
|
|
598
|
+
if (typeof searchTerm !== "string") {
|
|
599
|
+
cancel2("Operation cancelled");
|
|
600
|
+
process.exit(0);
|
|
601
|
+
}
|
|
602
|
+
const packages = await searchPackages(searchTerm);
|
|
603
|
+
if (packages.length === 0) {
|
|
604
|
+
console.log(chalk2.yellow("No packages found"));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const selected = await select2({
|
|
608
|
+
message: "Select a package:",
|
|
609
|
+
options: packages.map((pkg) => ({
|
|
610
|
+
value: pkg.name,
|
|
611
|
+
label: pkg.name,
|
|
612
|
+
hint: pkg.description
|
|
613
|
+
}))
|
|
614
|
+
});
|
|
615
|
+
if (typeof selected !== "string") {
|
|
616
|
+
cancel2("Operation cancelled");
|
|
617
|
+
process.exit(0);
|
|
618
|
+
}
|
|
619
|
+
packageName = selected;
|
|
620
|
+
}
|
|
621
|
+
const packageInfo = await getPackageInfo(packageName);
|
|
622
|
+
console.log(chalk2.cyan("\n\u{1F4E6} Package Details:"));
|
|
623
|
+
console.log(chalk2.gray("\u2500".repeat(50)));
|
|
624
|
+
console.log(`${chalk2.bold("Name:")} ${packageInfo.name}`);
|
|
625
|
+
console.log(`${chalk2.bold("Version:")} ${packageInfo.version}`);
|
|
626
|
+
console.log(`${chalk2.bold("Description:")} ${packageInfo.description}`);
|
|
627
|
+
console.log(`${chalk2.bold("Author:")} ${packageInfo.author}`);
|
|
628
|
+
console.log(`${chalk2.bold("Type:")} ${packageInfo.type}`);
|
|
629
|
+
console.log(`${chalk2.bold("Requires:")} ${packageInfo.requires}`);
|
|
630
|
+
console.log(chalk2.gray("\u2500".repeat(50)));
|
|
631
|
+
const shouldInstall = await confirm2({
|
|
632
|
+
message: "Install this package?",
|
|
633
|
+
initialValue: true
|
|
634
|
+
});
|
|
635
|
+
if (typeof shouldInstall !== "boolean" || !shouldInstall) {
|
|
636
|
+
cancel2("Operation cancelled");
|
|
637
|
+
process.exit(0);
|
|
638
|
+
}
|
|
639
|
+
await installPackage(packageInfo);
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (error instanceof Error && error.message !== "Operation cancelled") {
|
|
642
|
+
console.error(chalk2.red("Error:"), error.message);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
async function searchPackages(query) {
|
|
647
|
+
try {
|
|
648
|
+
const response = await axios.get(`${MARKETPLACE_API}/search`, {
|
|
649
|
+
params: { q: query }
|
|
650
|
+
});
|
|
651
|
+
return response.data.data || [];
|
|
652
|
+
} catch (error) {
|
|
653
|
+
console.log(chalk2.yellow("Note: Marketplace not available, searching local templates..."));
|
|
654
|
+
return getLocalTemplates();
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async function getPackageInfo(name) {
|
|
658
|
+
try {
|
|
659
|
+
const response = await axios.get(`${MARKETPLACE_API}/packages/${name}`);
|
|
660
|
+
return response.data.data;
|
|
661
|
+
} catch (error) {
|
|
662
|
+
const local = getLocalTemplates().find((p) => p.name === name);
|
|
663
|
+
if (!local) {
|
|
664
|
+
throw new Error(`Package "${name}" not found`);
|
|
665
|
+
}
|
|
666
|
+
return local;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
async function installPackage(packageInfo) {
|
|
670
|
+
const tasks = new Listr2([
|
|
671
|
+
{
|
|
672
|
+
title: "Downloading package",
|
|
673
|
+
task: async () => {
|
|
674
|
+
const downloadDir = path2.join(process.cwd(), ".exiloncms-temp");
|
|
675
|
+
await fs2.ensureDir(downloadDir);
|
|
676
|
+
const zipPath = path2.join(downloadDir, `${packageInfo.name}.zip`);
|
|
677
|
+
if (packageInfo.download_url) {
|
|
678
|
+
const response = await axios({
|
|
679
|
+
method: "GET",
|
|
680
|
+
url: packageInfo.download_url,
|
|
681
|
+
responseType: "stream"
|
|
682
|
+
});
|
|
683
|
+
const writer = fs2.createWriteStream(zipPath);
|
|
684
|
+
response.data.pipe(writer);
|
|
685
|
+
return new Promise((resolve, reject) => {
|
|
686
|
+
writer.on("finish", resolve);
|
|
687
|
+
writer.on("error", reject);
|
|
688
|
+
});
|
|
689
|
+
} else {
|
|
690
|
+
throw new Error("This package must be installed manually");
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
title: "Extracting package",
|
|
696
|
+
task: async () => {
|
|
697
|
+
const targetDir = packageInfo.type === "plugin" ? path2.join(process.cwd(), "plugins", packageInfo.name) : path2.join(process.cwd(), "themes", packageInfo.name);
|
|
698
|
+
await fs2.ensureDir(targetDir);
|
|
699
|
+
await execa2("unzip", ["-o", path2.join(process.cwd(), ".exiloncms-temp", `${packageInfo.name}.zip`), "-d", targetDir]);
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
title: "Installing dependencies",
|
|
704
|
+
task: async () => {
|
|
705
|
+
const packageDir = packageInfo.type === "plugin" ? path2.join(process.cwd(), "plugins", packageInfo.name) : path2.join(process.cwd(), "themes", packageInfo.name);
|
|
706
|
+
const composerJson = path2.join(packageDir, "composer.json");
|
|
707
|
+
const packageJson = path2.join(packageDir, "package.json");
|
|
708
|
+
if (await fs2.pathExists(composerJson)) {
|
|
709
|
+
await execa2("composer", ["install"], { cwd: packageDir });
|
|
710
|
+
}
|
|
711
|
+
if (await fs2.pathExists(packageJson)) {
|
|
712
|
+
await execa2("npm", ["install"], { cwd: packageDir });
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
title: "Running migrations",
|
|
718
|
+
task: async () => {
|
|
719
|
+
if (packageInfo.type === "plugin") {
|
|
720
|
+
await execa2("php", ["artisan", "migrate"], { cwd: process.cwd() });
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
title: "Cleaning up",
|
|
726
|
+
task: async () => {
|
|
727
|
+
await fs2.remove(path2.join(process.cwd(), ".exiloncms-temp"));
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
]);
|
|
731
|
+
await tasks.run();
|
|
732
|
+
}
|
|
733
|
+
function getLocalTemplates() {
|
|
734
|
+
return [
|
|
735
|
+
{
|
|
736
|
+
name: "blog",
|
|
737
|
+
version: "1.0.0",
|
|
738
|
+
description: "Full-featured blog plugin",
|
|
739
|
+
author: "ExilonCMS",
|
|
740
|
+
type: "plugin",
|
|
741
|
+
download_url: "",
|
|
742
|
+
requires: ">=1.0.0"
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: "gaming-theme",
|
|
746
|
+
version: "1.0.0",
|
|
747
|
+
description: "Gaming server theme",
|
|
748
|
+
author: "ExilonCMS",
|
|
749
|
+
type: "theme",
|
|
750
|
+
download_url: "",
|
|
751
|
+
requires: ">=1.0.0"
|
|
752
|
+
}
|
|
753
|
+
];
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/commands/update.ts
|
|
757
|
+
import { confirm as confirm3, cancel as cancel3, multiselect as multiselect2 } from "@clack/prompts";
|
|
758
|
+
import chalk3 from "chalk";
|
|
759
|
+
import { execa as execa3 } from "execa";
|
|
760
|
+
import fs3 from "fs-extra";
|
|
761
|
+
import path3 from "path";
|
|
762
|
+
import axios2 from "axios";
|
|
763
|
+
import { Listr as Listr3 } from "listr2";
|
|
764
|
+
var MARKETPLACE_API2 = "https://marketplace.exiloncms.fr/api/v1";
|
|
765
|
+
async function updateCommand(options) {
|
|
766
|
+
try {
|
|
767
|
+
let updatesAvailable = [];
|
|
768
|
+
const tasks = new Listr3([
|
|
769
|
+
{
|
|
770
|
+
title: "Checking for updates",
|
|
771
|
+
task: async () => {
|
|
772
|
+
updatesAvailable = await checkForUpdates();
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
]);
|
|
776
|
+
await tasks.run();
|
|
777
|
+
if (updatesAvailable.length === 0) {
|
|
778
|
+
console.log(chalk3.green("\u2713 Everything is up to date!"));
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
console.log(chalk3.cyan("\n\u{1F4E6} Available Updates:"));
|
|
782
|
+
console.log(chalk3.gray("\u2500".repeat(60)));
|
|
783
|
+
updatesAvailable.forEach((update) => {
|
|
784
|
+
const icon = update.type === "core" ? "\u2699\uFE0F" : update.type === "plugin" ? "\u{1F50C}" : "\u{1F3A8}";
|
|
785
|
+
console.log(`${icon} ${chalk3.bold(update.name)}`);
|
|
786
|
+
console.log(` ${chalk3.gray(update.currentVersion)} \u2192 ${chalk3.green(update.latestVersion)}`);
|
|
787
|
+
console.log();
|
|
788
|
+
});
|
|
789
|
+
const selectedUpdates = await multiselect2({
|
|
790
|
+
message: "Select updates to install (space to select, enter to continue):",
|
|
791
|
+
options: updatesAvailable.map((update) => ({
|
|
792
|
+
value: update.name,
|
|
793
|
+
label: update.name,
|
|
794
|
+
hint: `${update.currentVersion} \u2192 ${update.latestVersion}`
|
|
795
|
+
})),
|
|
796
|
+
required: false
|
|
797
|
+
});
|
|
798
|
+
if (!Array.isArray(selectedUpdates)) {
|
|
799
|
+
cancel3("Operation cancelled");
|
|
800
|
+
process.exit(0);
|
|
801
|
+
}
|
|
802
|
+
if (selectedUpdates.length === 0) {
|
|
803
|
+
console.log(chalk3.yellow("No updates selected"));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const shouldProceed = await confirm3({
|
|
807
|
+
message: `Install ${selectedUpdates.length} update(s)?`,
|
|
808
|
+
initialValue: true
|
|
809
|
+
});
|
|
810
|
+
if (typeof shouldProceed !== "boolean" || !shouldProceed) {
|
|
811
|
+
cancel3("Operation cancelled");
|
|
812
|
+
process.exit(0);
|
|
813
|
+
}
|
|
814
|
+
await installUpdates(updatesAvailable.filter((u) => selectedUpdates.includes(u.name)));
|
|
815
|
+
} catch (error) {
|
|
816
|
+
if (error instanceof Error && error.message !== "Operation cancelled") {
|
|
817
|
+
console.error(chalk3.red("Error:"), error.message);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async function checkForUpdates() {
|
|
822
|
+
const updates = [];
|
|
823
|
+
try {
|
|
824
|
+
const coreUpdate = await checkCoreUpdate();
|
|
825
|
+
if (coreUpdate) {
|
|
826
|
+
updates.push(coreUpdate);
|
|
827
|
+
}
|
|
828
|
+
} catch (error) {
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
const pluginsDir = path3.join(process.cwd(), "plugins");
|
|
832
|
+
if (await fs3.pathExists(pluginsDir)) {
|
|
833
|
+
const plugins = await fs3.readdir(pluginsDir);
|
|
834
|
+
for (const plugin of plugins) {
|
|
835
|
+
const pluginJson = path3.join(pluginsDir, plugin, "plugin.json");
|
|
836
|
+
if (await fs3.pathExists(pluginJson)) {
|
|
837
|
+
const info = await fs3.readJson(pluginJson);
|
|
838
|
+
const update = await checkPackageUpdate(plugin, info.version, "plugin");
|
|
839
|
+
if (update) {
|
|
840
|
+
updates.push(update);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
} catch (error) {
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
const themesDir = path3.join(process.cwd(), "themes");
|
|
849
|
+
if (await fs3.pathExists(themesDir)) {
|
|
850
|
+
const themes = await fs3.readdir(themesDir);
|
|
851
|
+
for (const theme of themes) {
|
|
852
|
+
const themeJson = path3.join(themesDir, theme, "theme.json");
|
|
853
|
+
if (await fs3.pathExists(themeJson)) {
|
|
854
|
+
const info = await fs3.readJson(themeJson);
|
|
855
|
+
const update = await checkPackageUpdate(theme, info.version, "theme");
|
|
856
|
+
if (update) {
|
|
857
|
+
updates.push(update);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
} catch (error) {
|
|
863
|
+
}
|
|
864
|
+
return updates;
|
|
865
|
+
}
|
|
866
|
+
async function checkCoreUpdate() {
|
|
867
|
+
try {
|
|
868
|
+
const response = await axios2.get(`${MARKETPLACE_API2}/core/latest`);
|
|
869
|
+
const latest = response.data.data;
|
|
870
|
+
const currentVersion = await getCurrentCoreVersion();
|
|
871
|
+
if (currentVersion !== latest.version) {
|
|
872
|
+
return {
|
|
873
|
+
name: "ExilonCMS Core",
|
|
874
|
+
currentVersion,
|
|
875
|
+
latestVersion: latest.version,
|
|
876
|
+
type: "core"
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
} catch (error) {
|
|
880
|
+
}
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
async function checkPackageUpdate(name, currentVersion, type) {
|
|
884
|
+
try {
|
|
885
|
+
const response = await axios2.get(`${MARKETPLACE_API2}/packages/${name}`);
|
|
886
|
+
const latest = response.data.data;
|
|
887
|
+
if (currentVersion !== latest.version) {
|
|
888
|
+
return {
|
|
889
|
+
name,
|
|
890
|
+
currentVersion,
|
|
891
|
+
latestVersion: latest.version,
|
|
892
|
+
type
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
} catch (error) {
|
|
896
|
+
}
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
async function getCurrentCoreVersion() {
|
|
900
|
+
try {
|
|
901
|
+
const composerJson = await fs3.readJson(path3.join(process.cwd(), "composer.json"));
|
|
902
|
+
const packageJson = await fs3.readJson(path3.join(process.cwd(), "package.json"));
|
|
903
|
+
return packageJson.version || "0.0.0";
|
|
904
|
+
} catch (error) {
|
|
905
|
+
return "0.0.0";
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
async function installUpdates(updates) {
|
|
909
|
+
for (const update of updates) {
|
|
910
|
+
const tasks = new Listr3([
|
|
911
|
+
{
|
|
912
|
+
title: `Updating ${update.name}`,
|
|
913
|
+
task: async () => {
|
|
914
|
+
if (update.type === "core") {
|
|
915
|
+
await updateCore(update);
|
|
916
|
+
} else {
|
|
917
|
+
await updatePackage(update);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
]);
|
|
922
|
+
await tasks.run();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
async function updateCore(update) {
|
|
926
|
+
try {
|
|
927
|
+
await execa3("git", ["pull", "origin", "main"], { cwd: process.cwd() });
|
|
928
|
+
await execa3("composer", ["install"], { cwd: process.cwd() });
|
|
929
|
+
await execa3("npm", ["install"], { cwd: process.cwd() });
|
|
930
|
+
await execa3("php", ["artisan", "migrate", "--force"], { cwd: process.cwd() });
|
|
931
|
+
} catch (error) {
|
|
932
|
+
throw new Error("Failed to update core. Make sure you have git installed.");
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
async function updatePackage(update) {
|
|
936
|
+
const packageDir = update.type === "plugin" ? path3.join(process.cwd(), "plugins", update.name) : path3.join(process.cwd(), "themes", update.name);
|
|
937
|
+
if (!await fs3.pathExists(packageDir)) {
|
|
938
|
+
throw new Error(`${update.name} is not installed`);
|
|
939
|
+
}
|
|
940
|
+
console.log(chalk3.yellow(`Please update ${update.name} manually or reinstall from marketplace`));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/commands/list.ts
|
|
944
|
+
import { select as select3 } from "@clack/prompts";
|
|
945
|
+
import chalk4 from "chalk";
|
|
946
|
+
import fs4 from "fs-extra";
|
|
947
|
+
import path4 from "path";
|
|
948
|
+
async function listCommand() {
|
|
949
|
+
try {
|
|
950
|
+
const packages = await getInstalledPackages();
|
|
951
|
+
if (packages.length === 0) {
|
|
952
|
+
console.log(chalk4.yellow("No plugins or themes installed"));
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const filter = await select3({
|
|
956
|
+
message: "Show:",
|
|
957
|
+
options: [
|
|
958
|
+
{ value: "all", label: "\u{1F4E6} All Packages" },
|
|
959
|
+
{ value: "plugins", label: "\u{1F50C} Plugins Only" },
|
|
960
|
+
{ value: "themes", label: "\u{1F3A8} Themes Only" }
|
|
961
|
+
],
|
|
962
|
+
initialValue: "all"
|
|
963
|
+
});
|
|
964
|
+
if (typeof filter !== "string") {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const filtered = filter === "all" ? packages : packages.filter((p) => p.type === (filter === "plugins" ? "plugin" : "theme"));
|
|
968
|
+
console.log(chalk4.cyan("\n\u{1F4E6} Installed Packages:"));
|
|
969
|
+
console.log(chalk4.gray("\u2500".repeat(70)));
|
|
970
|
+
const plugins = filtered.filter((p) => p.type === "plugin");
|
|
971
|
+
const themes = filtered.filter((p) => p.type === "theme");
|
|
972
|
+
if (plugins.length > 0 && (filter === "all" || filter === "plugins")) {
|
|
973
|
+
console.log(chalk4.bold("\n\u{1F50C} Plugins:"));
|
|
974
|
+
plugins.forEach((pkg) => {
|
|
975
|
+
const status = pkg.enabled ? chalk4.green("\u2713 Enabled") : chalk4.gray("\u2717 Disabled");
|
|
976
|
+
console.log(`
|
|
977
|
+
${chalk4.bold(pkg.name)} ${status}`);
|
|
978
|
+
console.log(` ${chalk4.gray("Version:")} ${pkg.version}`);
|
|
979
|
+
console.log(` ${chalk4.gray("Description:")} ${pkg.description}`);
|
|
980
|
+
console.log(` ${chalk4.gray("Author:")} ${pkg.author}`);
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
if (themes.length > 0 && (filter === "all" || filter === "themes")) {
|
|
984
|
+
console.log(chalk4.bold("\n\u{1F3A8} Themes:"));
|
|
985
|
+
themes.forEach((pkg) => {
|
|
986
|
+
const active = pkg.enabled ? chalk4.green("\u2713 Active") : chalk4.gray("\u2717 Inactive");
|
|
987
|
+
console.log(`
|
|
988
|
+
${chalk4.bold(pkg.name)} ${active}`);
|
|
989
|
+
console.log(` ${chalk4.gray("Version:")} ${pkg.version}`);
|
|
990
|
+
console.log(` ${chalk4.gray("Description:")} ${pkg.description}`);
|
|
991
|
+
console.log(` ${chalk4.gray("Author:")} ${pkg.author}`);
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
console.log(chalk4.gray("\n" + "\u2500".repeat(70)));
|
|
995
|
+
console.log(chalk4.gray(`Total: ${filtered.length} package(s)`));
|
|
996
|
+
} catch (error) {
|
|
997
|
+
if (error instanceof Error) {
|
|
998
|
+
console.error(chalk4.red("Error:"), error.message);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
async function getInstalledPackages() {
|
|
1003
|
+
const packages = [];
|
|
1004
|
+
const pluginsDir = path4.join(process.cwd(), "plugins");
|
|
1005
|
+
if (await fs4.pathExists(pluginsDir)) {
|
|
1006
|
+
const plugins = await fs4.readdir(pluginsDir);
|
|
1007
|
+
for (const plugin of plugins) {
|
|
1008
|
+
const pluginJson = path4.join(pluginsDir, plugin, "plugin.json");
|
|
1009
|
+
if (await fs4.pathExists(pluginJson)) {
|
|
1010
|
+
try {
|
|
1011
|
+
const info = await fs4.readJson(pluginJson);
|
|
1012
|
+
packages.push({
|
|
1013
|
+
name: info.name || plugin,
|
|
1014
|
+
version: info.version || "unknown",
|
|
1015
|
+
description: info.description || "",
|
|
1016
|
+
author: info.author || "Unknown",
|
|
1017
|
+
type: "plugin",
|
|
1018
|
+
enabled: true
|
|
1019
|
+
// Would check from database
|
|
1020
|
+
});
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const themesDir = path4.join(process.cwd(), "themes");
|
|
1027
|
+
if (await fs4.pathExists(themesDir)) {
|
|
1028
|
+
const themes = await fs4.readdir(themesDir);
|
|
1029
|
+
for (const theme of themes) {
|
|
1030
|
+
const themeJson = path4.join(themesDir, theme, "theme.json");
|
|
1031
|
+
if (await fs4.pathExists(themeJson)) {
|
|
1032
|
+
try {
|
|
1033
|
+
const info = await fs4.readJson(themeJson);
|
|
1034
|
+
packages.push({
|
|
1035
|
+
name: info.name || theme,
|
|
1036
|
+
version: info.version || "unknown",
|
|
1037
|
+
description: info.description || "",
|
|
1038
|
+
author: info.author || "Unknown",
|
|
1039
|
+
type: "theme",
|
|
1040
|
+
enabled: false
|
|
1041
|
+
// Would check from database
|
|
1042
|
+
});
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return packages;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/commands/build.ts
|
|
1052
|
+
import chalk5 from "chalk";
|
|
1053
|
+
import fs5 from "fs-extra";
|
|
1054
|
+
import path5 from "path";
|
|
1055
|
+
import { execa as execa4 } from "execa";
|
|
1056
|
+
import archiver from "archiver";
|
|
1057
|
+
import { Listr as Listr4 } from "listr2";
|
|
1058
|
+
async function buildCommand(options) {
|
|
1059
|
+
try {
|
|
1060
|
+
const packageType = await detectPackageType();
|
|
1061
|
+
if (!packageType) {
|
|
1062
|
+
console.log(chalk5.yellow("Not a valid plugin or theme directory"));
|
|
1063
|
+
console.log(chalk5.gray("Run this command from within a plugin/theme directory"));
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
const packageInfo = await getPackageInfo2();
|
|
1067
|
+
if (!packageInfo) {
|
|
1068
|
+
console.log(chalk5.yellow("Could not find package.json or plugin.json/theme.json"));
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
console.log(chalk5.cyan(`
|
|
1072
|
+
\u{1F528} Building ${packageType}: ${packageInfo.name}`));
|
|
1073
|
+
console.log(chalk5.gray(`Version: ${packageInfo.version}`));
|
|
1074
|
+
console.log();
|
|
1075
|
+
const tasks = new Listr4([
|
|
1076
|
+
{
|
|
1077
|
+
title: "Installing dependencies",
|
|
1078
|
+
task: async () => {
|
|
1079
|
+
const packageJsonPath = path5.join(process.cwd(), "package.json");
|
|
1080
|
+
if (await fs5.pathExists(packageJsonPath)) {
|
|
1081
|
+
await execa4("npm", ["install"], { cwd: process.cwd() });
|
|
1082
|
+
} else {
|
|
1083
|
+
return "Skipped (no package.json)";
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
title: "Building assets",
|
|
1089
|
+
task: async () => {
|
|
1090
|
+
const packageJsonPath = path5.join(process.cwd(), "package.json");
|
|
1091
|
+
if (await fs5.pathExists(packageJsonPath)) {
|
|
1092
|
+
const packageJson = await fs5.readJson(packageJsonPath);
|
|
1093
|
+
if (packageJson.scripts?.build) {
|
|
1094
|
+
await execa4("npm", ["run", "build"], { cwd: process.cwd() });
|
|
1095
|
+
} else {
|
|
1096
|
+
return "Skipped (no build script)";
|
|
1097
|
+
}
|
|
1098
|
+
} else {
|
|
1099
|
+
return "Skipped (no package.json)";
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
title: "Creating distribution package",
|
|
1105
|
+
task: async () => {
|
|
1106
|
+
await createDistPackage(packageInfo, packageType, options.format);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
]);
|
|
1110
|
+
await tasks.run();
|
|
1111
|
+
console.log(chalk5.green("\n\u2713 Build complete!"));
|
|
1112
|
+
console.log(chalk5.gray(`Package created: dist/${packageInfo.name}-${packageInfo.version}.${options.format}`));
|
|
1113
|
+
} catch (error) {
|
|
1114
|
+
if (error instanceof Error && error.message !== "Operation cancelled") {
|
|
1115
|
+
console.error(chalk5.red("Error:"), error.message);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
async function detectPackageType() {
|
|
1120
|
+
const pluginJson = path5.join(process.cwd(), "plugin.json");
|
|
1121
|
+
const themeJson = path5.join(process.cwd(), "theme.json");
|
|
1122
|
+
if (await fs5.pathExists(pluginJson)) {
|
|
1123
|
+
return "plugin";
|
|
1124
|
+
}
|
|
1125
|
+
if (await fs5.pathExists(themeJson)) {
|
|
1126
|
+
return "theme";
|
|
1127
|
+
}
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
async function getPackageInfo2() {
|
|
1131
|
+
const pluginJson = path5.join(process.cwd(), "plugin.json");
|
|
1132
|
+
const themeJson = path5.join(process.cwd(), "theme.json");
|
|
1133
|
+
const packageJson = path5.join(process.cwd(), "package.json");
|
|
1134
|
+
let info = {};
|
|
1135
|
+
if (await fs5.pathExists(pluginJson)) {
|
|
1136
|
+
info = { ...info, ...await fs5.readJson(pluginJson) };
|
|
1137
|
+
}
|
|
1138
|
+
if (await fs5.pathExists(themeJson)) {
|
|
1139
|
+
info = { ...info, ...await fs5.readJson(themeJson) };
|
|
1140
|
+
}
|
|
1141
|
+
if (await fs5.pathExists(packageJson)) {
|
|
1142
|
+
const pkg = await fs5.readJson(packageJson);
|
|
1143
|
+
info = {
|
|
1144
|
+
...info,
|
|
1145
|
+
name: info.name || pkg.name,
|
|
1146
|
+
version: info.version || pkg.version,
|
|
1147
|
+
description: info.description || pkg.description,
|
|
1148
|
+
author: info.author || pkg.author
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
if (!info.name || !info.version) {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
return info;
|
|
1155
|
+
}
|
|
1156
|
+
async function createDistPackage(packageInfo, packageType, format) {
|
|
1157
|
+
await fs5.ensureDir(path5.join(process.cwd(), "dist"));
|
|
1158
|
+
const outputFileName = `${packageInfo.name}-${packageInfo.version}.${format}`;
|
|
1159
|
+
const outputPath = path5.join(process.cwd(), "dist", outputFileName);
|
|
1160
|
+
const exclude = [
|
|
1161
|
+
"node_modules",
|
|
1162
|
+
"dist",
|
|
1163
|
+
".git",
|
|
1164
|
+
".gitignore",
|
|
1165
|
+
"coverage",
|
|
1166
|
+
".env",
|
|
1167
|
+
".env.local",
|
|
1168
|
+
"*.log",
|
|
1169
|
+
".DS_Store",
|
|
1170
|
+
"Thumbs.db"
|
|
1171
|
+
];
|
|
1172
|
+
if (format === "zip") {
|
|
1173
|
+
await createZip(outputPath, exclude);
|
|
1174
|
+
} else {
|
|
1175
|
+
await createTar(outputPath, exclude);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
async function createZip(outputPath, exclude) {
|
|
1179
|
+
return new Promise((resolve, reject) => {
|
|
1180
|
+
const output = fs5.createWriteStream(outputPath);
|
|
1181
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1182
|
+
output.on("close", () => resolve(null));
|
|
1183
|
+
archive.on("error", (err) => reject(err));
|
|
1184
|
+
archive.pipe(output);
|
|
1185
|
+
const rootPath = process.cwd();
|
|
1186
|
+
archive.directory(rootPath, false, {
|
|
1187
|
+
filter: (entry) => {
|
|
1188
|
+
const relativePath = path5.relative(rootPath, entry.path);
|
|
1189
|
+
const parts = relativePath.split(path5.sep);
|
|
1190
|
+
for (const excluded of exclude) {
|
|
1191
|
+
if (parts.some((p) => p === excluded.replace("*", ""))) {
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
if (relativePath.startsWith(excluded)) {
|
|
1195
|
+
return false;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
archive.finalize();
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
async function createTar(outputPath, exclude) {
|
|
1205
|
+
try {
|
|
1206
|
+
const excludeArgs = exclude.flatMap((e) => ["--exclude", e]);
|
|
1207
|
+
await execa4("tar", [
|
|
1208
|
+
"-czf",
|
|
1209
|
+
outputPath,
|
|
1210
|
+
...excludeArgs,
|
|
1211
|
+
"."
|
|
1212
|
+
], { cwd: process.cwd() });
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
throw new Error("tar command not available. Please use --format zip instead.");
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/index.ts
|
|
1219
|
+
var VERSION = "1.0.0";
|
|
1220
|
+
function showBanner() {
|
|
1221
|
+
const banner = gradient("cyan", "magenta")(`
|
|
1222
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
1223
|
+
\u2551 \u2551
|
|
1224
|
+
\u2551 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2551
|
|
1225
|
+
\u2551 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
|
|
1226
|
+
\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2551
|
|
1227
|
+
\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2551
|
|
1228
|
+
\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2551
|
|
1229
|
+
\u2551 \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u2551
|
|
1230
|
+
\u2551 \u2551
|
|
1231
|
+
\u2551 Developer CLI v${VERSION} \u2551
|
|
1232
|
+
\u2551 \u2551
|
|
1233
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
1234
|
+
`);
|
|
1235
|
+
console.log(banner);
|
|
1236
|
+
}
|
|
1237
|
+
var program = new Command();
|
|
1238
|
+
program.name("exiloncms").description(chalk6.cyan("ExilonCMS Developer CLI - Create plugins and themes with ease")).version(VERSION);
|
|
1239
|
+
program.command("new").description("Create a new plugin or theme").argument("[type]", "Type of project (plugin or theme)").action(async (type) => {
|
|
1240
|
+
intro(chalk6.cyan("\u{1F680} ExilonCMS CLI"));
|
|
1241
|
+
showBanner();
|
|
1242
|
+
await newCommand(type);
|
|
1243
|
+
outro(chalk6.green("\u2713 Done!"));
|
|
1244
|
+
});
|
|
1245
|
+
program.command("install").description("Install a plugin or theme from marketplace").argument("[name]", "Package name").action(async (name) => {
|
|
1246
|
+
intro(chalk6.cyan("\u{1F4E6} ExilonCMS Package Manager"));
|
|
1247
|
+
showBanner();
|
|
1248
|
+
await installCommand(name);
|
|
1249
|
+
outro(chalk6.green("\u2713 Package installed!"));
|
|
1250
|
+
});
|
|
1251
|
+
program.command("update").description("Update ExilonCMS core or packages").option("--core", "Update CMS core only").option("--packages", "Update installed packages only").action(async (options) => {
|
|
1252
|
+
intro(chalk6.cyan("\u{1F504} ExilonCMS Updater"));
|
|
1253
|
+
showBanner();
|
|
1254
|
+
await updateCommand(options);
|
|
1255
|
+
outro(chalk6.green("\u2713 Update complete!"));
|
|
1256
|
+
});
|
|
1257
|
+
program.command("list").description("List installed plugins and themes").action(async () => {
|
|
1258
|
+
intro(chalk6.cyan("\u{1F4CB} ExilonCMS Packages"));
|
|
1259
|
+
showBanner();
|
|
1260
|
+
await listCommand();
|
|
1261
|
+
});
|
|
1262
|
+
program.command("build").description("Build a plugin or theme for distribution").option("--format <format>", "Output format (zip|tar)", "zip").action(async (options) => {
|
|
1263
|
+
intro(chalk6.cyan("\u{1F528} ExilonCMS Builder"));
|
|
1264
|
+
showBanner();
|
|
1265
|
+
await buildCommand(options);
|
|
1266
|
+
outro(chalk6.green("\u2713 Build complete!"));
|
|
1267
|
+
});
|
|
1268
|
+
program.parse();
|
|
1269
|
+
if (!process.argv.slice(2).length) {
|
|
1270
|
+
intro(chalk6.cyan("\u{1F680} ExilonCMS CLI"));
|
|
1271
|
+
showBanner();
|
|
1272
|
+
program.outputHelp();
|
|
1273
|
+
outro(chalk6.gray('Tip: Run "exiloncms new" to create a new plugin or theme'));
|
|
1274
|
+
}
|
|
1275
|
+
//# sourceMappingURL=index.js.map
|