nibula 1.2.4 → 1.2.5

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.
Files changed (81) hide show
  1. package/.eleventy.js +81 -81
  2. package/.eleventyignore +3 -3
  3. package/.github/workflows/publish.yml +37 -0
  4. package/CHANGELOG.md +166 -148
  5. package/LICENSE +169 -169
  6. package/NOTICE +4 -4
  7. package/README.md +120 -123
  8. package/bin/create.js +507 -507
  9. package/bin/nibula.js +317 -305
  10. package/docs/Assistant CLI.md +65 -65
  11. package/docs/Backend.md +295 -295
  12. package/docs/Components.md +84 -84
  13. package/docs/Creating pages.md +64 -64
  14. package/docs/Deploy.md +189 -189
  15. package/docs/Head and SEO.md +138 -138
  16. package/docs/Javascript.md +50 -50
  17. package/docs/Styling with SCSS.md +141 -141
  18. package/nginx.conf +75 -75
  19. package/package.json +1 -1
  20. package/src/backend/.htaccess +6 -6
  21. package/src/backend/_core/composer.json +5 -5
  22. package/src/backend/_core/composer.lock +492 -492
  23. package/src/backend/_core/index.js +267 -267
  24. package/src/backend/_core/index.php +147 -147
  25. package/src/backend/_core/init.js +52 -52
  26. package/src/backend/_core/init.php +33 -33
  27. package/src/backend/_core/modules/RateLimiter.js +58 -58
  28. package/src/backend/_core/modules/RateLimiter.php +30 -30
  29. package/src/backend/_core/modules/Response.js +59 -59
  30. package/src/backend/_core/modules/Response.php +48 -48
  31. package/src/backend/api/protected/example-protected.js +23 -23
  32. package/src/backend/api/protected/example-protected.php +16 -16
  33. package/src/backend/api/public/example-public.js +24 -24
  34. package/src/backend/api/public/example-public.php +16 -16
  35. package/src/backend/backend-node.service.example +30 -30
  36. package/src/backend/database/Database.js +46 -46
  37. package/src/backend/database/Database.php +23 -23
  38. package/src/backend/example.config.js +37 -37
  39. package/src/backend/example.config.php +27 -27
  40. package/src/backend/package.json +18 -18
  41. package/src/backend/web.config +16 -16
  42. package/src/frontend/.htaccess +51 -51
  43. package/src/frontend/404.njk +17 -17
  44. package/src/frontend/assets/brand/favicon.svg +54 -54
  45. package/src/frontend/assets/brand/logo.svg +54 -54
  46. package/src/frontend/components/global/footer.njk +25 -25
  47. package/src/frontend/components/global/header.njk +6 -6
  48. package/src/frontend/components/welcome.njk +114 -114
  49. package/src/frontend/data/site.json +48 -48
  50. package/src/frontend/index.njk +8 -8
  51. package/src/frontend/js/modules/exampleModule.js +2 -2
  52. package/src/frontend/js/pages/404.js +6 -6
  53. package/src/frontend/js/pages/homepage.js +6 -6
  54. package/src/frontend/layouts/base.njk +132 -132
  55. package/src/frontend/layouts/page-components.njk +13 -13
  56. package/src/frontend/llms.njk +17 -17
  57. package/src/frontend/robots.njk +7 -7
  58. package/src/frontend/scss/modules/_animations.scss +24 -24
  59. package/src/frontend/scss/modules/_footer.scss +27 -27
  60. package/src/frontend/scss/modules/_global.scss +47 -47
  61. package/src/frontend/scss/modules/_header.scss +27 -27
  62. package/src/frontend/scss/modules/_mobile.scss +29 -29
  63. package/src/frontend/scss/modules/_root.scss +34 -34
  64. package/src/frontend/scss/modules/_typography.scss +14 -14
  65. package/src/frontend/scss/modules/frameworks/_bootstrap.scss +109 -109
  66. package/src/frontend/scss/modules/frameworks/_bulma.scss +108 -108
  67. package/src/frontend/scss/modules/frameworks/_foundation.scss +138 -139
  68. package/src/frontend/scss/modules/frameworks/_uikit.scss +109 -109
  69. package/src/frontend/scss/pages/404.scss +27 -27
  70. package/src/frontend/scss/pages/homepage.scss +22 -22
  71. package/src/frontend/sitemap.njk +17 -17
  72. package/src/frontend/ts/modules/exampleModule.ts +2 -2
  73. package/src/frontend/ts/pages/404.ts +6 -6
  74. package/src/frontend/ts/pages/homepage.ts +6 -6
  75. package/src/frontend/web.config +55 -55
  76. package/tools/config/messages.json +3 -1
  77. package/tools/res/templates/template.js +6 -6
  78. package/tools/res/templates/template.njk +8 -8
  79. package/tools/res/templates/template.scss +22 -22
  80. package/tools/res/templates/template.ts +6 -6
  81. package/tsconfig.json +24 -24
package/bin/nibula.js CHANGED
@@ -1,306 +1,318 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const https = require('https');
6
- const readline = require('readline');
7
- const { spawnSync } = require('child_process');
8
- const { findProjectRoot, NOT_INSIDE_PROJECT_MESSAGE } = require('../tools/lib/paths');
9
- const { color } = require('../tools/lib/colors');
10
-
11
- const pkg = require('../package.json');
12
-
13
- const [, , cmd, ...rest] = process.argv;
14
-
15
- const CREATE = path.join(__dirname, 'create.js');
16
- const ASSISTANT = path.join(__dirname, '..', 'tools', 'assistant.js');
17
- const BUILDJS = path.join(__dirname, '..', 'tools', 'buildJs.js');
18
- const CLEAN = path.join(__dirname, '..', 'tools', 'cleanOutput.js');
19
-
20
- const REGISTRY = 'https://registry.npmjs.org/nibula/latest';
21
- const CHECK_TIMEOUT = 2500;
22
- const SKIP_FLAG = '--skip-update-check';
23
-
24
- function run(script, args) {
25
- const res = spawnSync('node', [script, ...args], {
26
- stdio: 'inherit',
27
- cwd: process.cwd(),
28
- });
29
- process.exit(res.status ?? 0);
30
- }
31
-
32
- function requireProjectRoot() {
33
- const root = findProjectRoot(process.cwd());
34
- if (!root) {
35
- console.error(NOT_INSIDE_PROJECT_MESSAGE);
36
- process.exit(1);
37
- }
38
- return root;
39
- }
40
-
41
- function maybeDelegateToLocal(root) {
42
- const local = path.join(root, 'node_modules', 'nibula', 'bin', 'nibula.js');
43
- if (!fs.existsSync(local)) return;
44
-
45
- let localReal;
46
- let selfReal;
47
- try { localReal = fs.realpathSync(local); } catch { return; }
48
- try { selfReal = fs.realpathSync(__filename); } catch { selfReal = __filename; }
49
-
50
- if (localReal !== selfReal) {
51
- const res = spawnSync('node', [localReal, ...process.argv.slice(2)], {
52
- stdio: 'inherit',
53
- cwd: process.cwd(),
54
- });
55
- process.exit(res.status ?? 0);
56
- }
57
- }
58
-
59
- function enterProject() {
60
- const root = requireProjectRoot();
61
- maybeDelegateToLocal(root);
62
- return root;
63
- }
64
-
65
- function runNpm(root, scriptName) {
66
- const res = spawnSync('npm', ['run', scriptName], {
67
- stdio: 'inherit',
68
- cwd: root,
69
- shell: process.platform === 'win32',
70
- });
71
- if (res.error) console.error(res.error.message);
72
- process.exit(res.status ?? 0);
73
- }
74
-
75
- function fetchLatest() {
76
- return new Promise((resolve) => {
77
- const req = https.get(REGISTRY, (res) => {
78
- if (res.statusCode !== 200) {
79
- res.resume();
80
- resolve(null);
81
- return;
82
- }
83
- let data = '';
84
- res.on('data', (chunk) => { data += chunk; });
85
- res.on('end', () => {
86
- try {
87
- resolve(JSON.parse(data).version ?? null);
88
- } catch {
89
- resolve(null);
90
- }
91
- });
92
- });
93
- req.on('error', () => resolve(null));
94
- req.setTimeout(CHECK_TIMEOUT, () => {
95
- req.destroy();
96
- resolve(null);
97
- });
98
- });
99
- }
100
-
101
- function isOlder(current, latest) {
102
- const parse = (v) => String(v).split('-')[0].split('.').map(Number);
103
- const a = parse(current);
104
- const b = parse(latest);
105
- for (let i = 0; i < 3; i++) {
106
- const na = a[i] || 0;
107
- const nb = b[i] || 0;
108
- if (na < nb) return true;
109
- if (na > nb) return false;
110
- }
111
- return false;
112
- }
113
-
114
- async function checkVersion() {
115
- const latest = await fetchLatest();
116
- return {
117
- current: pkg.version,
118
- latest,
119
- behind: latest !== null && isOlder(pkg.version, latest),
120
- };
121
- }
122
-
123
- function ask(question) {
124
- return new Promise((resolve) => {
125
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
126
- rl.question(question, (answer) => {
127
- rl.close();
128
- resolve(answer.trim().toLowerCase());
129
- });
130
- });
131
- }
132
-
133
- function updateGlobal(version) {
134
- const target = version ? `nibula@${version}` : 'nibula@latest';
135
- const res = spawnSync('npm', ['install', '-g', target, '--prefer-online'], {
136
- stdio: 'inherit',
137
- shell: process.platform === 'win32',
138
- });
139
- return res.status ?? 0;
140
- }
141
-
142
- function globalNibulaPath() {
143
- const res = spawnSync('npm', ['root', '-g'], {
144
- encoding: 'utf8',
145
- shell: process.platform === 'win32',
146
- });
147
- if (res.status !== 0 || !res.stdout) return null;
148
- const candidate = path.join(res.stdout.trim(), 'nibula', 'bin', 'nibula.js');
149
- return fs.existsSync(candidate) ? candidate : null;
150
- }
151
-
152
- function reexecAfterUpdate(args) {
153
- const target = globalNibulaPath();
154
- if (!target) {
155
- console.error(`${color.red}Could not locate the updated Nibula installation.${color.reset}`);
156
- console.error(`Run "nib new ${args[1]}" again to continue.`);
157
- return 1;
158
- }
159
- const res = spawnSync(process.execPath, [target, ...args, SKIP_FLAG], {
160
- stdio: 'inherit',
161
- cwd: process.cwd(),
162
- });
163
- return res.status ?? 0;
164
- }
165
-
166
- function findExistingProject(baseDir, projectName) {
167
- let entries;
168
- try {
169
- entries = fs.readdirSync(baseDir, { withFileTypes: true });
170
- } catch {
171
- return null;
172
- }
173
- for (const entry of entries) {
174
- if (!entry.isDirectory()) continue;
175
- const pkgPath = path.join(baseDir, entry.name, 'package.json');
176
- if (!fs.existsSync(pkgPath)) continue;
177
- try {
178
- const data = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
179
- if (data && data.name === projectName) {
180
- return path.join(baseDir, entry.name);
181
- }
182
- } catch {
183
- // ...
184
- }
185
- }
186
- return null;
187
- }
188
-
189
- function usage(currentVersion) {
190
- console.log(`
191
- ${color.bold}${color.cyan}Nibula (${currentVersion})${color.reset} ${color.bold}by Michele Garofalo${color.reset}
192
-
193
- ${color.yellow}nib new <project-name>${color.reset} Create your new project
194
- ${color.yellow}nib cli${color.reset} Open the page-management assistant
195
- ${color.yellow}nib run${color.reset} Start the dev server and builds out folder runtime
196
- ${color.yellow}nib build${color.reset} Build the site out folder to publish
197
- ${color.yellow}nib clean${color.reset} Remove the output directory
198
- ${color.yellow}nib update${color.reset} Update to the latest version
199
- `);
200
- }
201
-
202
- async function main() {
203
- switch (cmd) {
204
- case 'new': {
205
- const args = rest.filter((a) => a !== SKIP_FLAG);
206
- const skipCheck = rest.includes(SKIP_FLAG);
207
-
208
- if (!args[0]) {
209
- console.error('Missing project name. Usage: nib new <project-name>');
210
- process.exit(1);
211
- }
212
-
213
- if (!skipCheck) {
214
- const info = await checkVersion();
215
- if (info.behind) {
216
- console.log(`\nA newer version of Nibula is available: ${info.current} → ${info.latest}`);
217
- if (process.stdin.isTTY) {
218
- const answer = await ask('Update before creating the project? [Y/n] ');
219
- if (answer === '' || answer === 'y' || answer === 'yes') {
220
- const code = updateGlobal(info.latest);
221
- if (code !== 0) process.exit(code);
222
- console.log(`\n${color.green}Updated.${color.reset} Continuing with the new version...\n`);
223
- process.exit(reexecAfterUpdate(['new', args[0]]));
224
- }
225
- } else {
226
- console.log('Run "nib update" to update.\n');
227
- }
228
- }
229
- }
230
-
231
- const existing = findExistingProject(process.cwd(), args[0]);
232
- if (existing) {
233
- console.log(`\n${color.yellow}A project named "${args[0]}" already exists:${color.reset} ${existing}`);
234
- let overwrite = false;
235
- if (process.stdin.isTTY) {
236
- const answer = await ask('Do you want to overwrite it? [y/N] ');
237
- overwrite = (answer === 'y' || answer === 'yes');
238
- } else {
239
- console.log('Run in a TTY to confirm overwrite.');
240
- }
241
- if (!overwrite) {
242
- console.log('Stopped creating the new project');
243
- process.exit(0);
244
- }
245
- try {
246
- fs.rmSync(existing, { recursive: true, force: true });
247
- console.log(`${color.green}Removed existing project.${color.reset}`);
248
- } catch (err) {
249
- console.error(`${color.red}Failed to remove existing project:${color.reset} ${err.message}`);
250
- process.exit(1);
251
- }
252
- }
253
-
254
- run(CREATE, [args[0]]);
255
- break;
256
- }
257
- case 'cli': {
258
- enterProject();
259
- run(ASSISTANT, []);
260
- break;
261
- }
262
- case 'run': {
263
- const root = enterProject();
264
- runNpm(root, 'serve');
265
- break;
266
- }
267
- case 'build': {
268
- const root = enterProject();
269
- runNpm(root, 'build');
270
- break;
271
- }
272
- case 'build-js': {
273
- enterProject();
274
- run(BUILDJS, rest);
275
- break;
276
- }
277
- case 'clean': {
278
- enterProject();
279
- run(CLEAN, []);
280
- break;
281
- }
282
- case 'update': {
283
- const info = await checkVersion();
284
- if (info.latest !== null && !info.behind) {
285
- console.log(`Already on the latest version (${info.current}).`);
286
- process.exit(0);
287
- }
288
- process.exit(updateGlobal(info.latest));
289
- }
290
- case undefined:
291
- case 'help':
292
- case '-help':
293
- case '--help':
294
- case 'h':
295
- case '-h': {
296
- usage(pkg.version);
297
- break;
298
- }
299
- default:
300
- console.error(`${color.red}\nUnknown command:${color.reset} ${cmd}`);
301
- usage(pkg.version);
302
- process.exit(1);
303
- }
304
- }
305
-
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const https = require('https');
6
+ const readline = require('readline');
7
+ const { spawnSync } = require('child_process');
8
+ const { findProjectRoot, NOT_INSIDE_PROJECT_MESSAGE } = require('../tools/lib/paths');
9
+ const { color } = require('../tools/lib/colors');
10
+ const { message } = require('../tools/lib/logger');
11
+
12
+ const pkg = require('../package.json');
13
+
14
+ const [, , cmd, ...rest] = process.argv;
15
+
16
+ const CREATE = path.join(__dirname, 'create.js');
17
+ const ASSISTANT = path.join(__dirname, '..', 'tools', 'assistant.js');
18
+ const BUILDJS = path.join(__dirname, '..', 'tools', 'buildJs.js');
19
+ const CLEAN = path.join(__dirname, '..', 'tools', 'cleanOutput.js');
20
+
21
+ const REGISTRY = 'https://registry.npmjs.org/nibula/latest';
22
+ const CHECK_TIMEOUT = 2500;
23
+ const SKIP_FLAG = '--skip-update-check';
24
+
25
+ function run(script, args) {
26
+ const res = spawnSync('node', [script, ...args], {
27
+ stdio: 'inherit',
28
+ cwd: process.cwd(),
29
+ });
30
+ process.exit(res.status ?? 0);
31
+ }
32
+
33
+ function requireProjectRoot() {
34
+ const root = findProjectRoot(process.cwd());
35
+ if (!root) {
36
+ console.error(NOT_INSIDE_PROJECT_MESSAGE);
37
+ process.exit(1);
38
+ }
39
+ return root;
40
+ }
41
+
42
+ function requireOutsideProject() {
43
+ const root = findProjectRoot(process.cwd());
44
+ if (!root) return;
45
+
46
+ console.error(`\n${color.red}${message('project.alreadyInside')}${color.reset}`);
47
+ console.error(`${color.dim}${message('project.alreadyInsideHint')}${color.reset}\n`);
48
+ process.exit(1);
49
+ }
50
+
51
+ function maybeDelegateToLocal(root) {
52
+ const local = path.join(root, 'node_modules', 'nibula', 'bin', 'nibula.js');
53
+ if (!fs.existsSync(local)) return;
54
+
55
+ let localReal;
56
+ let selfReal;
57
+ try { localReal = fs.realpathSync(local); } catch { return; }
58
+ try { selfReal = fs.realpathSync(__filename); } catch { selfReal = __filename; }
59
+
60
+ if (localReal !== selfReal) {
61
+ const res = spawnSync('node', [localReal, ...process.argv.slice(2)], {
62
+ stdio: 'inherit',
63
+ cwd: process.cwd(),
64
+ });
65
+ process.exit(res.status ?? 0);
66
+ }
67
+ }
68
+
69
+ function enterProject() {
70
+ const root = requireProjectRoot();
71
+ maybeDelegateToLocal(root);
72
+ return root;
73
+ }
74
+
75
+ function runNpm(root, scriptName) {
76
+ const res = spawnSync('npm', ['run', scriptName], {
77
+ stdio: 'inherit',
78
+ cwd: root,
79
+ shell: process.platform === 'win32',
80
+ });
81
+ if (res.error) console.error(res.error.message);
82
+ process.exit(res.status ?? 0);
83
+ }
84
+
85
+ function fetchLatest() {
86
+ return new Promise((resolve) => {
87
+ const req = https.get(REGISTRY, (res) => {
88
+ if (res.statusCode !== 200) {
89
+ res.resume();
90
+ resolve(null);
91
+ return;
92
+ }
93
+ let data = '';
94
+ res.on('data', (chunk) => { data += chunk; });
95
+ res.on('end', () => {
96
+ try {
97
+ resolve(JSON.parse(data).version ?? null);
98
+ } catch {
99
+ resolve(null);
100
+ }
101
+ });
102
+ });
103
+ req.on('error', () => resolve(null));
104
+ req.setTimeout(CHECK_TIMEOUT, () => {
105
+ req.destroy();
106
+ resolve(null);
107
+ });
108
+ });
109
+ }
110
+
111
+ function isOlder(current, latest) {
112
+ const parse = (v) => String(v).split('-')[0].split('.').map(Number);
113
+ const a = parse(current);
114
+ const b = parse(latest);
115
+ for (let i = 0; i < 3; i++) {
116
+ const na = a[i] || 0;
117
+ const nb = b[i] || 0;
118
+ if (na < nb) return true;
119
+ if (na > nb) return false;
120
+ }
121
+ return false;
122
+ }
123
+
124
+ async function checkVersion() {
125
+ const latest = await fetchLatest();
126
+ return {
127
+ current: pkg.version,
128
+ latest,
129
+ behind: latest !== null && isOlder(pkg.version, latest),
130
+ };
131
+ }
132
+
133
+ function ask(question) {
134
+ return new Promise((resolve) => {
135
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
136
+ rl.question(question, (answer) => {
137
+ rl.close();
138
+ resolve(answer.trim().toLowerCase());
139
+ });
140
+ });
141
+ }
142
+
143
+ function updateGlobal(version) {
144
+ const target = version ? `nibula@${version}` : 'nibula@latest';
145
+ const res = spawnSync('npm', ['install', '-g', target, '--prefer-online'], {
146
+ stdio: 'inherit',
147
+ shell: process.platform === 'win32',
148
+ });
149
+ return res.status ?? 0;
150
+ }
151
+
152
+ function globalNibulaPath() {
153
+ const res = spawnSync('npm', ['root', '-g'], {
154
+ encoding: 'utf8',
155
+ shell: process.platform === 'win32',
156
+ });
157
+ if (res.status !== 0 || !res.stdout) return null;
158
+ const candidate = path.join(res.stdout.trim(), 'nibula', 'bin', 'nibula.js');
159
+ return fs.existsSync(candidate) ? candidate : null;
160
+ }
161
+
162
+ function reexecAfterUpdate(args) {
163
+ const target = globalNibulaPath();
164
+ if (!target) {
165
+ console.error(`${color.red}Could not locate the updated Nibula installation.${color.reset}`);
166
+ console.error(`Run "nib new ${args[1]}" again to continue.`);
167
+ return 1;
168
+ }
169
+ const res = spawnSync(process.execPath, [target, ...args, SKIP_FLAG], {
170
+ stdio: 'inherit',
171
+ cwd: process.cwd(),
172
+ });
173
+ return res.status ?? 0;
174
+ }
175
+
176
+ function findExistingProject(baseDir, projectName) {
177
+ let entries;
178
+ try {
179
+ entries = fs.readdirSync(baseDir, { withFileTypes: true });
180
+ } catch {
181
+ return null;
182
+ }
183
+ for (const entry of entries) {
184
+ if (!entry.isDirectory()) continue;
185
+ const pkgPath = path.join(baseDir, entry.name, 'package.json');
186
+ if (!fs.existsSync(pkgPath)) continue;
187
+ try {
188
+ const data = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
189
+ if (data && data.name === projectName) {
190
+ return path.join(baseDir, entry.name);
191
+ }
192
+ } catch {
193
+ // ...
194
+ }
195
+ }
196
+ return null;
197
+ }
198
+
199
+ function usage(currentVersion) {
200
+ console.log(`
201
+ ${color.bold}${color.cyan}Nibula (${currentVersion})${color.reset} ${color.bold}by Michele Garofalo${color.reset}
202
+
203
+ ${color.yellow}nib new <project-name>${color.reset} Create your new project
204
+ ${color.yellow}nib cli${color.reset} Open the page-management assistant
205
+ ${color.yellow}nib run${color.reset} Start the dev server and builds out folder runtime
206
+ ${color.yellow}nib build${color.reset} Build the site out folder to publish
207
+ ${color.yellow}nib clean${color.reset} Remove the output directory
208
+ ${color.yellow}nib update${color.reset} Update to the latest version
209
+ `);
210
+ }
211
+
212
+ async function main() {
213
+ switch (cmd) {
214
+ case 'new': {
215
+ const args = rest.filter((a) => a !== SKIP_FLAG);
216
+ const skipCheck = rest.includes(SKIP_FLAG);
217
+
218
+ if (!args[0]) {
219
+ console.error('Missing project name. Usage: nib new <project-name>');
220
+ process.exit(1);
221
+ }
222
+
223
+ requireOutsideProject();
224
+
225
+ if (!skipCheck) {
226
+ const info = await checkVersion();
227
+ if (info.behind) {
228
+ console.log(`\nA newer version of Nibula is available: ${info.current} → ${info.latest}`);
229
+ if (process.stdin.isTTY) {
230
+ const answer = await ask('Update before creating the project? [Y/n] ');
231
+ if (answer === '' || answer === 'y' || answer === 'yes') {
232
+ const code = updateGlobal(info.latest);
233
+ if (code !== 0) process.exit(code);
234
+ console.log(`\n${color.green}Updated.${color.reset} Continuing with the new version...\n`);
235
+ process.exit(reexecAfterUpdate(['new', args[0]]));
236
+ }
237
+ } else {
238
+ console.log('Run "nib update" to update.\n');
239
+ }
240
+ }
241
+ }
242
+
243
+ const existing = findExistingProject(process.cwd(), args[0]);
244
+ if (existing) {
245
+ console.log(`\n${color.yellow}A project named "${args[0]}" already exists:${color.reset} ${existing}`);
246
+ let overwrite = false;
247
+ if (process.stdin.isTTY) {
248
+ const answer = await ask('Do you want to overwrite it? [y/N] ');
249
+ overwrite = (answer === 'y' || answer === 'yes');
250
+ } else {
251
+ console.log('Run in a TTY to confirm overwrite.');
252
+ }
253
+ if (!overwrite) {
254
+ console.log('Stopped creating the new project');
255
+ process.exit(0);
256
+ }
257
+ try {
258
+ fs.rmSync(existing, { recursive: true, force: true });
259
+ console.log(`${color.green}Removed existing project.${color.reset}`);
260
+ } catch (err) {
261
+ console.error(`${color.red}Failed to remove existing project:${color.reset} ${err.message}`);
262
+ process.exit(1);
263
+ }
264
+ }
265
+
266
+ run(CREATE, [args[0]]);
267
+ break;
268
+ }
269
+ case 'cli': {
270
+ enterProject();
271
+ run(ASSISTANT, []);
272
+ break;
273
+ }
274
+ case 'run': {
275
+ const root = enterProject();
276
+ runNpm(root, 'serve');
277
+ break;
278
+ }
279
+ case 'build': {
280
+ const root = enterProject();
281
+ runNpm(root, 'build');
282
+ break;
283
+ }
284
+ case 'build-js': {
285
+ enterProject();
286
+ run(BUILDJS, rest);
287
+ break;
288
+ }
289
+ case 'clean': {
290
+ enterProject();
291
+ run(CLEAN, []);
292
+ break;
293
+ }
294
+ case 'update': {
295
+ const info = await checkVersion();
296
+ if (info.latest !== null && !info.behind) {
297
+ console.log(`Already on the latest version (${info.current}).`);
298
+ process.exit(0);
299
+ }
300
+ process.exit(updateGlobal(info.latest));
301
+ }
302
+ case undefined:
303
+ case 'help':
304
+ case '-help':
305
+ case '--help':
306
+ case 'h':
307
+ case '-h': {
308
+ usage(pkg.version);
309
+ break;
310
+ }
311
+ default:
312
+ console.error(`${color.red}\nUnknown command:${color.reset} ${cmd}`);
313
+ usage(pkg.version);
314
+ process.exit(1);
315
+ }
316
+ }
317
+
306
318
  main();