nibula 1.2.3 โ 1.2.4
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/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/bin/create.js +1 -1
- package/bin/nibula.js +3 -6
- package/package.json +1 -1
- package/tools/assistant.js +122 -151
- package/tools/buildJs.js +39 -37
- package/tools/cleanOutput.js +23 -25
- package/tools/cli/prompt.js +40 -0
- package/tools/cli/ui.js +74 -0
- package/tools/config/messages.json +76 -0
- package/tools/config/settings.json +135 -0
- package/tools/lib/colors.js +17 -0
- package/tools/lib/files.js +65 -0
- package/tools/lib/logger.js +22 -0
- package/tools/lib/outputPath.js +141 -0
- package/tools/lib/pageActions.js +138 -0
- package/tools/lib/pageArtifacts.js +46 -0
- package/tools/lib/pageComponents.js +88 -0
- package/tools/lib/paths.js +61 -0
- package/tools/lib/project.js +54 -0
- package/tools/lib/siteData.js +93 -0
- package/tools/lib/text.js +47 -0
- package/tools/lib/validation.js +39 -0
- package/tools/res/templates/template.js +3 -1
- package/tools/res/templates/template.ts +3 -1
- package/tools/modules/constants.js +0 -66
- package/tools/modules/pageComponents.js +0 -77
- package/tools/modules/updateData.js +0 -90
- package/tools/modules/updateOutputPath.js +0 -133
- package/tools/modules/updatePage.js +0 -162
- package/tools/modules/utils.js +0 -27
- package/tools/modules/validation.js +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to Nibula are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.2.4] - 2026-07-31
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Refactor interno di `tools/`: la logica รจ divisa in `lib/`, `cli/` e
|
|
12
|
+
`config/`, con valori e messaggi estratti in `settings.json` e
|
|
13
|
+
`messages.json`. Nessun cambiamento di comportamento.
|
|
14
|
+
- La CLI usa una palette uniforme: il colore resta solo sul riquadro del
|
|
15
|
+
titolo, i numeri delle voci sono attenuati.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- L'ultima riga del menu non chiudeva il codice colore `dim`, che restava
|
|
19
|
+
attivo sulla riga successiva.
|
|
20
|
+
|
|
8
21
|
## [1.2.3] - 2026-07-29
|
|
9
22
|
|
|
10
23
|
### Fixed
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Building a website from scratch involves a lot of moving parts: templating, buil
|
|
|
21
21
|
- ๐ชถ **Lightweight by default** โ SCSS frameworks can be filtered so you ship only what you actually use
|
|
22
22
|
- ๐ **Open source** โ free to use, free to modify, free to share
|
|
23
23
|
|
|
24
|
-

|
|
25
25
|

|
|
26
26
|

|
|
27
27
|
|
package/bin/create.js
CHANGED
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const readline = require('readline');
|
|
6
6
|
const { writeSync } = require('fs');
|
|
7
7
|
const { spawnSync } = require('child_process');
|
|
8
|
-
const { color } = require('../tools/
|
|
8
|
+
const { color } = require('../tools/lib/colors');
|
|
9
9
|
|
|
10
10
|
// โโ PATHS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
11
11
|
|
package/bin/nibula.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const https = require('https');
|
|
6
6
|
const readline = require('readline');
|
|
7
7
|
const { spawnSync } = require('child_process');
|
|
8
|
-
const { findProjectRoot,
|
|
8
|
+
const { findProjectRoot, NOT_INSIDE_PROJECT_MESSAGE } = require('../tools/lib/paths');
|
|
9
|
+
const { color } = require('../tools/lib/colors');
|
|
9
10
|
|
|
10
11
|
const pkg = require('../package.json');
|
|
11
12
|
|
|
@@ -41,8 +42,6 @@ function maybeDelegateToLocal(root) {
|
|
|
41
42
|
const local = path.join(root, 'node_modules', 'nibula', 'bin', 'nibula.js');
|
|
42
43
|
if (!fs.existsSync(local)) return;
|
|
43
44
|
|
|
44
|
-
// Compare REAL paths so symlinks (e.g. from `npm link`) resolve to the same
|
|
45
|
-
// physical file; otherwise the guard would loop forever under npm link.
|
|
46
45
|
let localReal;
|
|
47
46
|
let selfReal;
|
|
48
47
|
try { localReal = fs.realpathSync(local); } catch { return; }
|
|
@@ -150,8 +149,6 @@ function globalNibulaPath() {
|
|
|
150
149
|
return fs.existsSync(candidate) ? candidate : null;
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
// Riesegue il comando con la versione appena installata: questo processo ha
|
|
154
|
-
// ancora in memoria il codice vecchio, quindi non puรฒ scaffoldare da solo.
|
|
155
152
|
function reexecAfterUpdate(args) {
|
|
156
153
|
const target = globalNibulaPath();
|
|
157
154
|
if (!target) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nibula",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "A beginner-friendly static site generator built on Eleventy that stays close to vanilla HTML, CSS and JS โ with a page-management CLI, an optional PHP backend, and SEO files generated for you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static-site-generator",
|
package/tools/assistant.js
CHANGED
|
@@ -1,152 +1,123 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
console.log(`\n${color.dim}The project may be incomplete or created with a different Nibula version.${color.reset}`);
|
|
125
|
-
rl.close();
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
while (true) {
|
|
130
|
-
renderMenu();
|
|
131
|
-
|
|
132
|
-
const choice = await ask(`${color.cyan}โฏ${color.reset} Choose an option: `);
|
|
133
|
-
if (choice === null) break;
|
|
134
|
-
|
|
135
|
-
const action = MENU_ACTIONS[choice];
|
|
136
|
-
if (!action) {
|
|
137
|
-
console.log(`\n${color.red}โ Invalid option.${color.reset}`);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
await action();
|
|
143
|
-
} catch (err) {
|
|
144
|
-
console.log(`\n${color.red}โ Unexpected error: ${err.message}${color.reset}`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
rl.close();
|
|
149
|
-
process.exit(0);
|
|
150
|
-
}
|
|
151
|
-
|
|
1
|
+
const settings = require('./config/settings.json');
|
|
2
|
+
const { message } = require('./lib/logger');
|
|
3
|
+
const { toKebabCase } = require('./lib/text');
|
|
4
|
+
const { validatePageName, validateOutputPath, checkRequiredFiles } = require('./lib/validation');
|
|
5
|
+
const { addPage, removePage, renamePage } = require('./lib/pageActions');
|
|
6
|
+
const { pageExists } = require('./lib/pageArtifacts');
|
|
7
|
+
const { updateOutputPath, getCurrentOutputPath } = require('./lib/outputPath');
|
|
8
|
+
const { ask, confirm, close } = require('./cli/prompt');
|
|
9
|
+
const ui = require('./cli/ui');
|
|
10
|
+
|
|
11
|
+
const NEW_LINE = '\n';
|
|
12
|
+
const NO_PREFIX = '';
|
|
13
|
+
|
|
14
|
+
async function askPageName(prompt) {
|
|
15
|
+
const raw = await ask(prompt);
|
|
16
|
+
if (raw === null) return null;
|
|
17
|
+
|
|
18
|
+
const name = toKebabCase(raw);
|
|
19
|
+
const error = validatePageName(name);
|
|
20
|
+
if (error) {
|
|
21
|
+
ui.error(error);
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function handleCreate() {
|
|
28
|
+
const name = await askPageName(ui.promptLine(message('cli.createPrompt')));
|
|
29
|
+
if (name) addPage(name);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function handleRemove() {
|
|
33
|
+
const name = await askPageName(ui.promptLine(message('cli.removePrompt')));
|
|
34
|
+
if (!name) return;
|
|
35
|
+
|
|
36
|
+
if (!pageExists(name)) {
|
|
37
|
+
ui.warning(message('cli.pageNotFound', { name }));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const confirmed = await confirm(message('cli.confirmRemove', { name }));
|
|
42
|
+
if (!confirmed) {
|
|
43
|
+
ui.notice(message('cli.cancelled'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
removePage(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function handleRename() {
|
|
50
|
+
const oldName = await askPageName(ui.promptLine(message('cli.renameFromPrompt')));
|
|
51
|
+
if (!oldName) return;
|
|
52
|
+
|
|
53
|
+
const newName = await askPageName(ui.promptLine(message('cli.renameToPrompt'), NO_PREFIX));
|
|
54
|
+
if (!newName) return;
|
|
55
|
+
|
|
56
|
+
if (oldName === newName) {
|
|
57
|
+
ui.warning(message('cli.sameName'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
renamePage(oldName, newName);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function handleOutputPath() {
|
|
64
|
+
const current = getCurrentOutputPath();
|
|
65
|
+
const prefix = current
|
|
66
|
+
? ui.noticePrefix(message('cli.currentOutputPath', { path: current }))
|
|
67
|
+
: NEW_LINE;
|
|
68
|
+
|
|
69
|
+
const input = await ask(ui.promptLine(message('cli.outputPathPrompt'), prefix));
|
|
70
|
+
if (input === null) return;
|
|
71
|
+
|
|
72
|
+
const error = validateOutputPath(input);
|
|
73
|
+
if (error) {
|
|
74
|
+
ui.error(error);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
updateOutputPath(input);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ACTIONS = {
|
|
81
|
+
createPage: handleCreate,
|
|
82
|
+
removePage: handleRemove,
|
|
83
|
+
renamePage: handleRename,
|
|
84
|
+
outputPath: handleOutputPath,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
function findAction(choice) {
|
|
88
|
+
const item = settings.cli.menu.find((entry) => entry.key === choice);
|
|
89
|
+
return item ? ACTIONS[item.action] : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function main() {
|
|
93
|
+
const missing = checkRequiredFiles();
|
|
94
|
+
if (missing.length > 0) {
|
|
95
|
+
ui.renderMissingFiles(missing);
|
|
96
|
+
close();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
while (true) {
|
|
101
|
+
ui.renderMenu();
|
|
102
|
+
|
|
103
|
+
const choice = await ask(ui.promptLine(message('cli.menuPrompt'), NO_PREFIX));
|
|
104
|
+
if (choice === null) break;
|
|
105
|
+
|
|
106
|
+
const action = findAction(choice);
|
|
107
|
+
if (!action) {
|
|
108
|
+
ui.error(message('cli.invalidOption'));
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await action();
|
|
114
|
+
} catch (error) {
|
|
115
|
+
ui.error(message('cli.unexpectedError', { error: error.message }));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
close();
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
152
123
|
main();
|
package/tools/buildJs.js
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
esbuild.
|
|
37
|
-
}
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const esbuild = require('esbuild');
|
|
3
|
+
const glob = require('glob');
|
|
4
|
+
const settings = require('./config/settings.json');
|
|
5
|
+
const { PATHS } = require('./lib/paths');
|
|
6
|
+
const { readPackageJson, allScriptEntries } = require('./lib/project');
|
|
7
|
+
|
|
8
|
+
const WATCH_FLAG = '--watch';
|
|
9
|
+
const PATH_SEPARATOR = '/';
|
|
10
|
+
|
|
11
|
+
function toPosix(target) {
|
|
12
|
+
return target.split(path.sep).join(PATH_SEPARATOR);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function collectEntryPoints() {
|
|
16
|
+
return allScriptEntries().flatMap((entries) => glob.sync(toPosix(path.join(PATHS.root, entries))));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const packageJson = readPackageJson();
|
|
20
|
+
const outputDirectory = packageJson[settings.packageJson.outputKey] || settings.project.defaultOutputDirectory;
|
|
21
|
+
const watch = process.argv.includes(WATCH_FLAG);
|
|
22
|
+
const entryPoints = collectEntryPoints();
|
|
23
|
+
|
|
24
|
+
if (entryPoints.length === 0) {
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const options = {
|
|
29
|
+
entryPoints,
|
|
30
|
+
bundle: true,
|
|
31
|
+
outdir: path.join(PATHS.root, outputDirectory, ...settings.build.scriptOutput.split(PATH_SEPARATOR)),
|
|
32
|
+
minify: !watch,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
if (watch) {
|
|
36
|
+
esbuild.context(options).then((context) => context.watch()).catch(() => process.exit(1));
|
|
37
|
+
} else {
|
|
38
|
+
esbuild.build(options).catch(() => process.exit(1));
|
|
39
|
+
}
|
package/tools/cleanOutput.js
CHANGED
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
console.log(`(i) nothing to clean โ ${outputDir}`);
|
|
25
|
-
}
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const settings = require('./config/settings.json');
|
|
3
|
+
const { PATHS } = require('./lib/paths');
|
|
4
|
+
const { readPackageJson } = require('./lib/project');
|
|
5
|
+
const { removeDirectory } = require('./lib/files');
|
|
6
|
+
const { log } = require('./lib/logger');
|
|
7
|
+
|
|
8
|
+
const OUTPUT_KEY = settings.packageJson.outputKey;
|
|
9
|
+
|
|
10
|
+
const packageJson = readPackageJson();
|
|
11
|
+
|
|
12
|
+
if (!packageJson[OUTPUT_KEY]) {
|
|
13
|
+
log('output.missingConfiguration', { key: OUTPUT_KEY, file: settings.paths.packageJson });
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const outputDirectory = path.resolve(PATHS.root, packageJson[OUTPUT_KEY]);
|
|
18
|
+
|
|
19
|
+
if (removeDirectory(outputDirectory)) {
|
|
20
|
+
log('output.cleaned', { path: outputDirectory });
|
|
21
|
+
} else {
|
|
22
|
+
log('output.nothingToClean', { path: outputDirectory });
|
|
23
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const settings = require('../config/settings.json');
|
|
3
|
+
const { color } = require('../lib/colors');
|
|
4
|
+
|
|
5
|
+
const CONTROL_CHARACTERS_PATTERN = /[\x00-\x1F\x7F]/g;
|
|
6
|
+
const AFFIRMATIVE_PATTERN = /^y(es)?$/i;
|
|
7
|
+
const CONFIRM_HINT = settings.cli.confirmHint;
|
|
8
|
+
const EMPTY = '';
|
|
9
|
+
|
|
10
|
+
const readlineInterface = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
terminal: true,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function sanitizeInput(value) {
|
|
17
|
+
return (value ?? EMPTY).replace(CONTROL_CHARACTERS_PATTERN, EMPTY).trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ask(prompt) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const onClose = () => resolve(null);
|
|
23
|
+
readlineInterface.once('close', onClose);
|
|
24
|
+
readlineInterface.question(prompt, (answer) => {
|
|
25
|
+
readlineInterface.off('close', onClose);
|
|
26
|
+
resolve(sanitizeInput(answer));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function confirm(prompt) {
|
|
32
|
+
const answer = await ask(`${prompt} ${color.dim}${CONFIRM_HINT}${color.reset} `);
|
|
33
|
+
return AFFIRMATIVE_PATTERN.test((answer ?? EMPTY).trim());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function close() {
|
|
37
|
+
readlineInterface.close();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { ask, confirm, close };
|
package/tools/cli/ui.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const settings = require('../config/settings.json');
|
|
2
|
+
const { color } = require('../lib/colors');
|
|
3
|
+
const { message } = require('../lib/logger');
|
|
4
|
+
|
|
5
|
+
const SYMBOLS = settings.cli.symbols;
|
|
6
|
+
const COLORS = settings.cli.colors;
|
|
7
|
+
const MENU = settings.cli.menu;
|
|
8
|
+
const BOX_WIDTH = settings.cli.boxWidth;
|
|
9
|
+
const BOX_TITLE = settings.cli.title;
|
|
10
|
+
const BOX_TOP_LEFT = '\u256d';
|
|
11
|
+
const BOX_TOP_RIGHT = '\u256e';
|
|
12
|
+
const BOX_BOTTOM_LEFT = '\u2570';
|
|
13
|
+
const BOX_BOTTOM_RIGHT = '\u256f';
|
|
14
|
+
const BOX_HORIZONTAL = '\u2500';
|
|
15
|
+
const BOX_VERTICAL = '\u2502';
|
|
16
|
+
const NEW_LINE = '\n';
|
|
17
|
+
const SPACE = ' ';
|
|
18
|
+
|
|
19
|
+
function line(colorName, text) {
|
|
20
|
+
return `${color[colorName]}${text}${color.reset}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function promptLine(text, prefix = NEW_LINE) {
|
|
24
|
+
return `${prefix}${line(COLORS.prompt, SYMBOLS.prompt)} ${text}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function error(text) {
|
|
28
|
+
console.log(`${NEW_LINE}${line(COLORS.error, `${SYMBOLS.error} ${text}`)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function warning(text) {
|
|
32
|
+
console.log(`${NEW_LINE}${line(COLORS.warning, `${SYMBOLS.warning} ${text}`)}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function notice(text) {
|
|
36
|
+
console.log(`${NEW_LINE}${line(COLORS.notice, text)}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function noticePrefix(text) {
|
|
40
|
+
return `${NEW_LINE}${line(COLORS.notice, text)}${NEW_LINE}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function centeredTitle() {
|
|
44
|
+
const padding = BOX_WIDTH - BOX_TITLE.length;
|
|
45
|
+
const left = SPACE.repeat(Math.ceil(padding / 2));
|
|
46
|
+
const right = SPACE.repeat(Math.floor(padding / 2));
|
|
47
|
+
return `${left}${BOX_TITLE}${right}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderMenu() {
|
|
51
|
+
const border = BOX_HORIZONTAL.repeat(BOX_WIDTH);
|
|
52
|
+
|
|
53
|
+
console.log(`${NEW_LINE}${color[COLORS.box]}${color.bold}${BOX_TOP_LEFT}${border}${BOX_TOP_RIGHT}`);
|
|
54
|
+
console.log(`${BOX_VERTICAL}${centeredTitle()}${BOX_VERTICAL}`);
|
|
55
|
+
console.log(`${BOX_BOTTOM_LEFT}${border}${BOX_BOTTOM_RIGHT}${color.reset}${NEW_LINE}`);
|
|
56
|
+
|
|
57
|
+
for (const item of MENU) {
|
|
58
|
+
console.log(` ${line(COLORS.menuKey, `${item.key}.`)} ${item.label}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`${NEW_LINE}${line(COLORS.notice, message('cli.exitHint'))}${NEW_LINE}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function renderMissingFiles(items) {
|
|
65
|
+
console.log(`${NEW_LINE}${line(COLORS.error, `${SYMBOLS.error} ${message('cli.missingFiles')}`)}`);
|
|
66
|
+
|
|
67
|
+
for (const item of items) {
|
|
68
|
+
console.log(` ${line(COLORS.error, SYMBOLS.bullet)} ${item.label}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`${NEW_LINE}${line(COLORS.notice, message('cli.incompleteProject'))}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { promptLine, noticePrefix, error, warning, notice, renderMenu, renderMissingFiles };
|