custom-menu-cli 2.0.0 → 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 +43 -2
- package/README.md +43 -2
- 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,14 +88,41 @@ 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:
|
|
94
121
|
|
|
95
122
|
```json
|
|
96
123
|
{
|
|
97
|
-
"name": "
|
|
98
|
-
"description": "
|
|
124
|
+
"name": "custom-menu-cli",
|
|
125
|
+
"description": "JSON-based terminal menu",
|
|
99
126
|
"options": [
|
|
100
127
|
{
|
|
101
128
|
"id": "1",
|
|
@@ -114,6 +141,13 @@ O arquivo JSON que define o menu tem a seguinte estrutura:
|
|
|
114
141
|
"name": "Up Service",
|
|
115
142
|
"type": "action",
|
|
116
143
|
"command": "echo 'Up A'"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"id": "1.3",
|
|
147
|
+
"name": "Restart Project A (from inside)",
|
|
148
|
+
"type": "custom-action",
|
|
149
|
+
"idList": ["1.1", "1.2"],
|
|
150
|
+
"confirm": true
|
|
117
151
|
}
|
|
118
152
|
]
|
|
119
153
|
},
|
|
@@ -123,6 +157,13 @@ O arquivo JSON que define o menu tem a seguinte estrutura:
|
|
|
123
157
|
"type": "custom-action",
|
|
124
158
|
"idList": ["1.1", "1.2"],
|
|
125
159
|
"confirm": true
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"id": "3",
|
|
163
|
+
"name": "Restart Project A (Nested)",
|
|
164
|
+
"type": "custom-action",
|
|
165
|
+
"idList": ["1.3"],
|
|
166
|
+
"confirm": true
|
|
126
167
|
}
|
|
127
168
|
]
|
|
128
169
|
}
|
package/README.md
CHANGED
|
@@ -89,14 +89,41 @@ 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:
|
|
95
122
|
|
|
96
123
|
```json
|
|
97
124
|
{
|
|
98
|
-
"name": "
|
|
99
|
-
"description": "
|
|
125
|
+
"name": "custom-menu-cli",
|
|
126
|
+
"description": "JSON-based terminal menu",
|
|
100
127
|
"options": [
|
|
101
128
|
{
|
|
102
129
|
"id": "1",
|
|
@@ -115,6 +142,13 @@ The JSON file that defines the menu has the following structure:
|
|
|
115
142
|
"name": "Up Service",
|
|
116
143
|
"type": "action",
|
|
117
144
|
"command": "echo 'Up A'"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "1.3",
|
|
148
|
+
"name": "Restart Project A (from inside)",
|
|
149
|
+
"type": "custom-action",
|
|
150
|
+
"idList": ["1.1", "1.2"],
|
|
151
|
+
"confirm": true
|
|
118
152
|
}
|
|
119
153
|
]
|
|
120
154
|
},
|
|
@@ -124,6 +158,13 @@ The JSON file that defines the menu has the following structure:
|
|
|
124
158
|
"type": "custom-action",
|
|
125
159
|
"idList": ["1.1", "1.2"],
|
|
126
160
|
"confirm": true
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "3",
|
|
164
|
+
"name": "Restart Project A (Nested)",
|
|
165
|
+
"type": "custom-action",
|
|
166
|
+
"idList": ["1.3"],
|
|
167
|
+
"confirm": true
|
|
127
168
|
}
|
|
128
169
|
]
|
|
129
170
|
}
|
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 };
|