custom-menu-cli 2.0.1 → 3.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/README-pt.md +27 -0
- package/README.md +27 -0
- package/index.js +4 -2
- package/package.json +1 -1
- package/src/configLoader.js +54 -11
- package/src/menuValidator.js +44 -0
- package/test_menus/1-project-a/1.1-down-service.json +7 -0
- package/test_menus/1-project-a/1.2-up-service.json +6 -0
- package/test_menus/1-project-a/1.3-restart-project-a.json +7 -0
- package/test_menus/2-restart-all.json +7 -0
- package/test_menus/3-restart-project-a-nested.json +7 -0
package/README-pt.md
CHANGED
|
@@ -88,6 +88,33 @@ async function iniciarMeuMenuCustomizado() {
|
|
|
88
88
|
iniciarMeuMenuCustomizado();
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
### 4. Geração de Menu Baseada em Pastas
|
|
92
|
+
|
|
93
|
+
O `custom-menu-cli` agora suporta a geração de menus a partir de uma pasta estruturada contendo arquivos JSON. Isso permite uma melhor organização e modularidade das suas definições de menu.
|
|
94
|
+
|
|
95
|
+
**Estrutura de Exemplo (`test_menus/`):**
|
|
96
|
+
```
|
|
97
|
+
test_menus/
|
|
98
|
+
├── 1-project-a/
|
|
99
|
+
│ ├── 1.1-down-service.json
|
|
100
|
+
│ ├── 1.2-up-service.json
|
|
101
|
+
│ └── 1.3-restart-project-a.json
|
|
102
|
+
├── 2-restart-all.json
|
|
103
|
+
└── 3-restart-project-a-nested.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Cada arquivo `.json` dentro da pasta (e suas subpastas) representa uma opção de menu. Os diretórios são automaticamente convertidos em opções do tipo `navigation`.
|
|
107
|
+
|
|
108
|
+
**Como usar:**
|
|
109
|
+
|
|
110
|
+
Basta passar o caminho para a sua pasta de menu como argumento:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
custom-menu-cli ./caminho/para/sua/pasta_de_menu
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
O CLI irá automaticamente descobrir e combinar todos os arquivos JSON válidos em uma única estrutura de menu.
|
|
117
|
+
|
|
91
118
|
## Estrutura do JSON
|
|
92
119
|
|
|
93
120
|
O arquivo JSON que define o menu tem a seguinte estrutura:
|
package/README.md
CHANGED
|
@@ -89,6 +89,33 @@ async function startMyCustomMenu() {
|
|
|
89
89
|
startMyCustomMenu();
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
### 4. Folder-based Menu Generation
|
|
93
|
+
|
|
94
|
+
The `custom-menu-cli` now supports generating menus from a structured folder containing JSON files. This allows for better organization and modularity of your menu definitions.
|
|
95
|
+
|
|
96
|
+
**Example Structure (`test_menus/`):**
|
|
97
|
+
```
|
|
98
|
+
test_menus/
|
|
99
|
+
├── 1-project-a/
|
|
100
|
+
│ ├── 1.1-down-service.json
|
|
101
|
+
│ ├── 1.2-up-service.json
|
|
102
|
+
│ └── 1.3-restart-project-a.json
|
|
103
|
+
├── 2-restart-all.json
|
|
104
|
+
└── 3-restart-project-a-nested.json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Each `.json` file within the folder (and its subfolders) represents a menu option. Directories are automatically converted into `navigation` type options.
|
|
108
|
+
|
|
109
|
+
**How to use:**
|
|
110
|
+
|
|
111
|
+
Simply pass the path to your menu folder as an argument:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
custom-menu-cli ./path/to/your/menu_folder
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The CLI will automatically discover and combine all valid JSON files into a single menu structure.
|
|
118
|
+
|
|
92
119
|
## JSON Structure
|
|
93
120
|
|
|
94
121
|
The JSON file that defines the menu has the following structure:
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const { showMenu, buildIdMap } = require('./src/menu.js');
|
|
|
5
5
|
const { displayHeader } = require('./src/header.js');
|
|
6
6
|
|
|
7
7
|
async function runCli(menuPath = null) {
|
|
8
|
-
const data = loadMenuConfig(menuPath);
|
|
8
|
+
const data = await loadMenuConfig(menuPath);
|
|
9
9
|
if (data.options) {
|
|
10
10
|
buildIdMap(data.options);
|
|
11
11
|
}
|
|
@@ -18,7 +18,9 @@ async function runCli(menuPath = null) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (require.main === module) {
|
|
21
|
-
|
|
21
|
+
(async () => {
|
|
22
|
+
await runCli();
|
|
23
|
+
})();
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
module.exports = { runCli };
|
package/package.json
CHANGED
package/src/configLoader.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const chalk = require('chalk');
|
|
4
|
+
const { validateMenuOption } = require('./menuValidator.js'); // Importar o validador
|
|
3
5
|
|
|
4
6
|
const defaultMenu = {
|
|
5
7
|
"name": "Example Menu",
|
|
@@ -15,7 +17,40 @@ const defaultMenu = {
|
|
|
15
17
|
]
|
|
16
18
|
};
|
|
17
19
|
|
|
18
|
-
function
|
|
20
|
+
async function buildMenuOptions(dir) {
|
|
21
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
22
|
+
const options = [];
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const fullPath = path.join(dir, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
const subOptions = await buildMenuOptions(fullPath);
|
|
28
|
+
options.push({
|
|
29
|
+
id: entry.name,
|
|
30
|
+
name: `=> ${entry.name.toUpperCase()}`,
|
|
31
|
+
type: 'navigation',
|
|
32
|
+
options: subOptions
|
|
33
|
+
});
|
|
34
|
+
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
35
|
+
const fileContent = await fs.readFile(fullPath, 'utf-8');
|
|
36
|
+
let option;
|
|
37
|
+
try {
|
|
38
|
+
option = JSON.parse(fileContent);
|
|
39
|
+
} catch (parseError) {
|
|
40
|
+
console.error(chalk.red(`Erro: Arquivo JSON malformado em: ${fullPath}`));
|
|
41
|
+
console.error(chalk.red(`Detalhes: ${parseError.message}`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
validateMenuOption(option, fullPath); // Chamar o validador
|
|
46
|
+
|
|
47
|
+
options.push(option);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return options;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function loadMenuConfig(menuPath = null) {
|
|
19
54
|
let data;
|
|
20
55
|
let finalPath = menuPath;
|
|
21
56
|
|
|
@@ -25,27 +60,35 @@ function loadMenuConfig(menuPath = null) {
|
|
|
25
60
|
finalPath = args[0];
|
|
26
61
|
}
|
|
27
62
|
|
|
28
|
-
if (finalPath
|
|
63
|
+
if (finalPath) {
|
|
29
64
|
try {
|
|
30
|
-
|
|
65
|
+
const stats = await fs.stat(finalPath);
|
|
66
|
+
if (stats.isDirectory()) {
|
|
67
|
+
data = {
|
|
68
|
+
name: "Dynamic Menu",
|
|
69
|
+
description: `Menu generated from folder: ${finalPath}`,
|
|
70
|
+
options: await buildMenuOptions(finalPath)
|
|
71
|
+
};
|
|
72
|
+
} else { // It's a file
|
|
73
|
+
data = JSON.parse(await fs.readFile(finalPath, 'utf-8'));
|
|
74
|
+
validateMenuOption(data, finalPath); // Validar menu de arquivo único também
|
|
75
|
+
}
|
|
31
76
|
} catch (error) {
|
|
32
|
-
console.log(chalk.red(`
|
|
77
|
+
console.log(chalk.red(`Erro ao processar o caminho: ${finalPath}`));
|
|
33
78
|
console.error(error);
|
|
34
79
|
process.exit(1);
|
|
35
80
|
}
|
|
36
|
-
} else if (
|
|
37
|
-
console.log(chalk.red(`File not found: ${finalPath}`));
|
|
38
|
-
process.exit(1);
|
|
39
|
-
} else if (fs.existsSync('./menu.json')) {
|
|
81
|
+
} else if (fs.existsSync('./menu.json')) { // This part remains sync for now
|
|
40
82
|
try {
|
|
41
83
|
data = JSON.parse(fs.readFileSync('./menu.json', 'utf-8'));
|
|
84
|
+
validateMenuOption(data, './menu.json'); // Validar menu.json padrão
|
|
42
85
|
} catch (error) {
|
|
43
|
-
console.log(chalk.red(`
|
|
86
|
+
console.log(chalk.red(`Erro ao analisar o arquivo JSON: ./menu.json`));
|
|
44
87
|
console.error(error);
|
|
45
88
|
process.exit(1);
|
|
46
89
|
}
|
|
47
90
|
} else {
|
|
48
|
-
console.log(chalk.yellow("
|
|
91
|
+
console.log(chalk.yellow("Nenhum 'menu.json' encontrado. Carregando menu de exemplo."));
|
|
49
92
|
data = defaultMenu;
|
|
50
93
|
}
|
|
51
94
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
function validateMenuOption(option, filePath) {
|
|
4
|
+
// --- Validation Logic ---
|
|
5
|
+
if (!option.id || typeof option.id !== 'string') {
|
|
6
|
+
console.error(chalk.red(`
|
|
7
|
+
Validation Error: 'id' missing or invalid in: ${filePath}`));
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
if (!option.name || typeof option.name !== 'string') {
|
|
11
|
+
console.error(chalk.red(`
|
|
12
|
+
Validation Error: 'name' missing or invalid in: ${filePath}`));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const validTypes = ['action', 'navigation', 'custom-action'];
|
|
16
|
+
if (!option.type || typeof option.type !== 'string' || !validTypes.includes(option.type)) {
|
|
17
|
+
console.error(chalk.red(`
|
|
18
|
+
Validation Error: 'type' missing or invalid (expected ${validTypes.join(', ')}) in: ${filePath}`));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (option.type === 'action') {
|
|
23
|
+
if (!option.command || typeof option.command !== 'string') {
|
|
24
|
+
console.error(chalk.red(`
|
|
25
|
+
Validation Error: 'command' missing or invalid for type 'action' in: ${filePath}`));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
} else if (option.type === 'custom-action') {
|
|
29
|
+
if (!Array.isArray(option.idList) || option.idList.length === 0) {
|
|
30
|
+
console.error(chalk.red(`
|
|
31
|
+
Validation Error: 'idList' missing or empty for type 'custom-action' in: ${filePath}`));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
} else if (option.type === 'navigation') {
|
|
35
|
+
if (!Array.isArray(option.options) || option.options.length === 0) {
|
|
36
|
+
console.error(chalk.red(`
|
|
37
|
+
Validation Error: 'options' missing or empty for type 'navigation' in: ${filePath}. Navigation must come from directories or have sub-options.`));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// --- End Validation Logic ---
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { validateMenuOption };
|