create-skateboard-app 1.1.3 → 1.2.1
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 +14 -0
- package/bin/cli.js +172 -65
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
1.2.1
|
|
2
|
+
|
|
3
|
+
Fix dash-prefixed flag values
|
|
4
|
+
Fix connection-string validation
|
|
5
|
+
Fix quiet-mode error output
|
|
6
|
+
Fix resolve absolute path
|
|
7
|
+
|
|
8
|
+
1.2.0
|
|
9
|
+
|
|
10
|
+
Add non-interactive CLI flags
|
|
11
|
+
Add --yes flag
|
|
12
|
+
Add --quiet flag
|
|
13
|
+
Add flag validation
|
|
14
|
+
|
|
1
15
|
1.1.3
|
|
2
16
|
auto uncomment for db url
|
|
3
17
|
|
package/bin/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
5
|
-
import { join } from 'path';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
6
|
import https from 'https';
|
|
7
7
|
import { createWriteStream } from 'fs';
|
|
8
8
|
import { createInterface } from 'readline';
|
|
@@ -19,21 +19,21 @@ const colors = {
|
|
|
19
19
|
bold: '\x1b[1m'
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
let log = (message, color = 'reset') => {
|
|
23
23
|
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
24
|
-
}
|
|
24
|
+
};
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
let error = (message) => {
|
|
27
27
|
log(`❌ ${message}`, 'red');
|
|
28
|
-
}
|
|
28
|
+
};
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
let success = (message) => {
|
|
31
31
|
log(`✅ ${message}`, 'green');
|
|
32
|
-
}
|
|
32
|
+
};
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
let info = (message) => {
|
|
35
35
|
log(`ℹ️ ${message}`, 'blue');
|
|
36
|
-
}
|
|
36
|
+
};
|
|
37
37
|
|
|
38
38
|
function checkCommand(command) {
|
|
39
39
|
try {
|
|
@@ -44,6 +44,65 @@ function checkCommand(command) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
const VALID_COLORS = ['blue', 'green', 'purple', 'red', 'orange', 'yellow', 'pink', 'cyan', 'black'];
|
|
48
|
+
const VALID_ICONS = ['command', 'house', 'zap', 'rocket', 'diamond', 'target', 'flame', 'star'];
|
|
49
|
+
const VALID_DATABASES = ['sqlite', 'postgresql', 'mongodb'];
|
|
50
|
+
|
|
51
|
+
function parseFlags(argv) {
|
|
52
|
+
const args = argv.slice(2);
|
|
53
|
+
const flags = { positional: null };
|
|
54
|
+
let i = 0;
|
|
55
|
+
|
|
56
|
+
while (i < args.length) {
|
|
57
|
+
const arg = args[i];
|
|
58
|
+
if (arg === '-y' || arg === '--yes') {
|
|
59
|
+
flags.yes = true;
|
|
60
|
+
} else if (arg === '--quiet' || arg === '-q') {
|
|
61
|
+
flags.quiet = true;
|
|
62
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
63
|
+
flags.help = true;
|
|
64
|
+
} else if (arg === '--version' || arg === '-v') {
|
|
65
|
+
flags.version = true;
|
|
66
|
+
} else if (arg.startsWith('--')) {
|
|
67
|
+
const key = arg.slice(2);
|
|
68
|
+
const knownValueFlags = ['name', 'tagline', 'color', 'icon', 'database', 'connection-string'];
|
|
69
|
+
if (knownValueFlags.includes(key) && i + 1 < args.length) {
|
|
70
|
+
i++;
|
|
71
|
+
const val = args[i];
|
|
72
|
+
if (key === 'name') flags.name = val;
|
|
73
|
+
else if (key === 'tagline') flags.tagline = val;
|
|
74
|
+
else if (key === 'color') flags.color = val;
|
|
75
|
+
else if (key === 'icon') flags.icon = val;
|
|
76
|
+
else if (key === 'database') flags.database = val;
|
|
77
|
+
else if (key === 'connection-string') flags.connectionString = val;
|
|
78
|
+
}
|
|
79
|
+
} else if (!arg.startsWith('-') && !flags.positional) {
|
|
80
|
+
flags.positional = arg;
|
|
81
|
+
}
|
|
82
|
+
i++;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return flags;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function validateFlags(flags) {
|
|
89
|
+
if (flags.color && !VALID_COLORS.includes(flags.color)) {
|
|
90
|
+
console.error(`Error: Invalid color "${flags.color}". Must be one of: ${VALID_COLORS.join(', ')}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
if (flags.icon && !VALID_ICONS.includes(flags.icon)) {
|
|
94
|
+
console.error(`Error: Invalid icon "${flags.icon}". Must be one of: ${VALID_ICONS.join(', ')}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
if (flags.database && !VALID_DATABASES.includes(flags.database)) {
|
|
98
|
+
console.error(`Error: Invalid database "${flags.database}". Must be one of: ${VALID_DATABASES.join(', ')}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
if (flags.connectionString && (!flags.database || flags.database === 'sqlite')) {
|
|
102
|
+
console.error('Warning: --connection-string is ignored when database is sqlite. Use --database postgresql or --database mongodb.');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
47
106
|
async function downloadTemplate(projectName) {
|
|
48
107
|
// Try multiple methods in order of preference
|
|
49
108
|
const methods = [
|
|
@@ -183,16 +242,21 @@ async function askYesNo(question, defaultYes = true) {
|
|
|
183
242
|
return answer.toLowerCase().startsWith('y');
|
|
184
243
|
}
|
|
185
244
|
|
|
186
|
-
async function collectProjectConfig(projectName) {
|
|
187
|
-
|
|
245
|
+
async function collectProjectConfig(projectName, flags = {}) {
|
|
246
|
+
const nonInteractive = flags.yes;
|
|
247
|
+
|
|
248
|
+
if (!nonInteractive) {
|
|
249
|
+
log(`\n${colors.bold}Let's configure your Skateboard app!${colors.reset}\n`);
|
|
250
|
+
}
|
|
188
251
|
|
|
189
252
|
// App name
|
|
190
|
-
const
|
|
253
|
+
const defaultAppName = projectName.split('-').map(word =>
|
|
191
254
|
word.charAt(0).toUpperCase() + word.slice(1)
|
|
192
|
-
).join(' ')
|
|
255
|
+
).join(' ');
|
|
256
|
+
const appName = flags.name || (nonInteractive ? defaultAppName : await ask('App display name', defaultAppName));
|
|
193
257
|
|
|
194
258
|
// Tagline
|
|
195
|
-
const tagline = await ask('App tagline', 'Try Something New');
|
|
259
|
+
const tagline = flags.tagline || (nonInteractive ? 'Try Something New' : await ask('App tagline', 'Try Something New'));
|
|
196
260
|
|
|
197
261
|
// App color selection
|
|
198
262
|
const colorChoices = [
|
|
@@ -206,8 +270,10 @@ async function collectProjectConfig(projectName) {
|
|
|
206
270
|
{ label: '🩵 Cyan', value: 'cyan' },
|
|
207
271
|
{ label: '⚫ Black', value: 'black' }
|
|
208
272
|
];
|
|
209
|
-
|
|
210
|
-
const selectedColor =
|
|
273
|
+
|
|
274
|
+
const selectedColor = flags.color
|
|
275
|
+
? colorChoices.find(c => c.value === flags.color)
|
|
276
|
+
: (nonInteractive ? colorChoices[0] : await askChoice('Choose your app color:', colorChoices));
|
|
211
277
|
|
|
212
278
|
// App icon
|
|
213
279
|
const iconChoices = [
|
|
@@ -220,8 +286,10 @@ async function collectProjectConfig(projectName) {
|
|
|
220
286
|
{ label: '🔥 Flame', value: 'flame' },
|
|
221
287
|
{ label: '⭐ Star', value: 'star' }
|
|
222
288
|
];
|
|
223
|
-
|
|
224
|
-
const selectedIcon =
|
|
289
|
+
|
|
290
|
+
const selectedIcon = flags.icon
|
|
291
|
+
? iconChoices.find(c => c.value === flags.icon)
|
|
292
|
+
: (nonInteractive ? iconChoices[0] : await askChoice('Choose an app icon:', iconChoices));
|
|
225
293
|
|
|
226
294
|
// Database selection
|
|
227
295
|
const databaseChoices = [
|
|
@@ -229,15 +297,19 @@ async function collectProjectConfig(projectName) {
|
|
|
229
297
|
{ label: '🐘 PostgreSQL', value: 'postgresql', connectionString: 'postgresql://user:password@localhost:5432/dbname' },
|
|
230
298
|
{ label: '🍃 MongoDB', value: 'mongodb', connectionString: 'mongodb://localhost:27017/dbname' }
|
|
231
299
|
];
|
|
232
|
-
|
|
233
|
-
const selectedDatabase =
|
|
300
|
+
|
|
301
|
+
const selectedDatabase = flags.database
|
|
302
|
+
? databaseChoices.find(c => c.value === flags.database)
|
|
303
|
+
: (nonInteractive ? databaseChoices[0] : await askChoice('Choose your database:', databaseChoices, 0));
|
|
234
304
|
|
|
235
305
|
// Get connection string for non-SQLite databases
|
|
236
|
-
let connectionString = '';
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
306
|
+
let connectionString = flags.connectionString || '';
|
|
307
|
+
if (!connectionString && !nonInteractive) {
|
|
308
|
+
if (selectedDatabase.value === 'postgresql') {
|
|
309
|
+
connectionString = await ask('PostgreSQL connection string (optional)', '');
|
|
310
|
+
} else if (selectedDatabase.value === 'mongodb') {
|
|
311
|
+
connectionString = await ask('MongoDB connection string (optional)', '');
|
|
312
|
+
}
|
|
241
313
|
}
|
|
242
314
|
|
|
243
315
|
// Default values for removed questions
|
|
@@ -288,19 +360,29 @@ function showHelp() {
|
|
|
288
360
|
${colors.bold}🛹 Create Skateboard App${colors.reset}
|
|
289
361
|
|
|
290
362
|
${colors.cyan}Usage:${colors.reset}
|
|
291
|
-
npx create-skateboard-app
|
|
363
|
+
npx create-skateboard-app [project-name] [options]
|
|
292
364
|
|
|
293
365
|
${colors.cyan}Arguments:${colors.reset}
|
|
294
|
-
project-name
|
|
366
|
+
project-name Optional project directory name (will prompt if not provided)
|
|
295
367
|
|
|
296
368
|
${colors.cyan}Options:${colors.reset}
|
|
297
|
-
--help, -h
|
|
298
|
-
--version, -v
|
|
369
|
+
--help, -h Show this help message
|
|
370
|
+
--version, -v Show version number
|
|
371
|
+
-y, --yes Accept all defaults, skip prompts
|
|
372
|
+
--quiet, -q Suppress decorative output, print JSON on success
|
|
373
|
+
--name <value> App display name
|
|
374
|
+
--tagline <value> App tagline
|
|
375
|
+
--color <value> App color (${VALID_COLORS.join(', ')})
|
|
376
|
+
--icon <value> App icon (${VALID_ICONS.join(', ')})
|
|
377
|
+
--database <value> Database type (${VALID_DATABASES.join(', ')})
|
|
378
|
+
--connection-string <v> Database connection string
|
|
299
379
|
|
|
300
380
|
${colors.cyan}Examples:${colors.reset}
|
|
301
|
-
npx create-skateboard-app
|
|
302
|
-
npx create-skateboard-app my-app
|
|
303
|
-
npx create-skateboard-app
|
|
381
|
+
npx create-skateboard-app # Interactive mode
|
|
382
|
+
npx create-skateboard-app my-app # With project name
|
|
383
|
+
npx create-skateboard-app my-app -y # All defaults, no prompts
|
|
384
|
+
npx create-skateboard-app my-app --color red --icon rocket -y # Custom values
|
|
385
|
+
npx create-skateboard-app my-app -y --quiet # CI/agent-friendly
|
|
304
386
|
`, 'reset');
|
|
305
387
|
}
|
|
306
388
|
|
|
@@ -310,25 +392,41 @@ function showVersion() {
|
|
|
310
392
|
}
|
|
311
393
|
|
|
312
394
|
async function main() {
|
|
313
|
-
//
|
|
314
|
-
const
|
|
315
|
-
|
|
395
|
+
// Parse flags from argv
|
|
396
|
+
const flags = parseFlags(process.argv);
|
|
397
|
+
validateFlags(flags);
|
|
316
398
|
|
|
317
399
|
// Handle help and version flags
|
|
318
|
-
if (
|
|
400
|
+
if (flags.help) {
|
|
319
401
|
showHelp();
|
|
320
402
|
process.exit(0);
|
|
321
403
|
}
|
|
322
404
|
|
|
323
|
-
if (
|
|
405
|
+
if (flags.version) {
|
|
324
406
|
showVersion();
|
|
325
407
|
process.exit(0);
|
|
326
408
|
}
|
|
327
409
|
|
|
328
|
-
//
|
|
410
|
+
// Quiet mode: override log functions to suppress output
|
|
411
|
+
const quiet = flags.quiet;
|
|
412
|
+
if (quiet) {
|
|
413
|
+
const noop = () => {};
|
|
414
|
+
log = noop;
|
|
415
|
+
error = (msg) => console.error(msg);
|
|
416
|
+
success = noop;
|
|
417
|
+
info = noop;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let projectName = flags.positional;
|
|
421
|
+
|
|
422
|
+
// If no project name provided, ask for it or use default
|
|
329
423
|
if (!projectName) {
|
|
330
|
-
|
|
331
|
-
|
|
424
|
+
if (flags.yes) {
|
|
425
|
+
projectName = 'my-skateboard-app';
|
|
426
|
+
} else {
|
|
427
|
+
log(`\n${colors.bold}🛹 Welcome to Skateboard App Creator!${colors.reset}\n`);
|
|
428
|
+
projectName = await ask('Project directory name', 'my-skateboard-app');
|
|
429
|
+
}
|
|
332
430
|
}
|
|
333
431
|
|
|
334
432
|
// Validate project name
|
|
@@ -351,7 +449,7 @@ async function main() {
|
|
|
351
449
|
await downloadTemplate(projectName);
|
|
352
450
|
|
|
353
451
|
// Step 2: Collect user configuration
|
|
354
|
-
const config = await collectProjectConfig(projectName);
|
|
452
|
+
const config = await collectProjectConfig(projectName, flags);
|
|
355
453
|
|
|
356
454
|
// Step 3: Update package.json
|
|
357
455
|
info('Updating package.json...');
|
|
@@ -446,7 +544,7 @@ async function main() {
|
|
|
446
544
|
|
|
447
545
|
// Step 7: Install dependencies
|
|
448
546
|
info('Installing dependencies...');
|
|
449
|
-
execSync(`cd ${projectName} && npm install`, { stdio: 'inherit' });
|
|
547
|
+
execSync(`cd ${projectName} && npm install`, { stdio: quiet ? 'pipe' : 'inherit' });
|
|
450
548
|
success('Dependencies installed');
|
|
451
549
|
|
|
452
550
|
// Step 8: Initialize git (if requested)
|
|
@@ -457,33 +555,42 @@ async function main() {
|
|
|
457
555
|
}
|
|
458
556
|
|
|
459
557
|
// Success message
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
log(`\n${colors.
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
558
|
+
if (quiet) {
|
|
559
|
+
const absolutePath = resolve(projectName);
|
|
560
|
+
console.log(JSON.stringify({ success: true, path: absolutePath }));
|
|
561
|
+
} else {
|
|
562
|
+
log(`\n${colors.bold}${colors.green}🎉 Success! Created ${config.appName}${colors.reset}\n`);
|
|
563
|
+
|
|
564
|
+
// Database-specific instructions (only if connection string not provided)
|
|
565
|
+
if (config.database.value === 'postgresql' && !config.connectionString) {
|
|
566
|
+
log(`\n${colors.yellow}📝 PostgreSQL Setup:${colors.reset}`);
|
|
567
|
+
log(` Update the ${colors.cyan}backend/.env${colors.reset} file with:`);
|
|
568
|
+
log(` ${colors.green}POSTGRES_URL=postgresql://username:password@localhost:5432/dbname${colors.reset}`);
|
|
569
|
+
} else if (config.database.value === 'mongodb' && !config.connectionString) {
|
|
570
|
+
log(`\n${colors.yellow}📝 MongoDB Setup:${colors.reset}`);
|
|
571
|
+
log(` Update the ${colors.cyan}backend/.env${colors.reset} file with:`);
|
|
572
|
+
log(` ${colors.green}MONGODB_URL=mongodb://localhost:27017/dbname${colors.reset}`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Stripe setup instructions
|
|
576
|
+
log(`\n${colors.yellow}💳 Stripe Setup:${colors.reset}`);
|
|
469
577
|
log(` Update the ${colors.cyan}backend/.env${colors.reset} file with:`);
|
|
470
|
-
log(` ${colors.green}
|
|
578
|
+
log(` ${colors.green}STRIPE_KEY=sk_test_your_stripe_secret_key_here${colors.reset}`);
|
|
579
|
+
log(` ${colors.green}STRIPE_ENDPOINT_SECRET=whsec_your_webhook_endpoint_secret_here${colors.reset}`);
|
|
580
|
+
log(` Step by Step Guide: ${colors.blue}https://github.com/stevederico/skateboard#-stripe-setup${colors.reset}`);
|
|
581
|
+
|
|
582
|
+
log(`\n${colors.bold}Get started with:${colors.reset}`, 'yellow');
|
|
583
|
+
log(`\n ${colors.cyan}cd ${projectName}${colors.reset}`);
|
|
584
|
+
log(` ${colors.cyan}npm run start${colors.reset}`);
|
|
585
|
+
log(`\n${colors.yellow}Happy skating! 🛹${colors.reset}\n`);
|
|
471
586
|
}
|
|
472
587
|
|
|
473
|
-
// Stripe setup instructions
|
|
474
|
-
log(`\n${colors.yellow}💳 Stripe Setup:${colors.reset}`);
|
|
475
|
-
log(` Update the ${colors.cyan}backend/.env${colors.reset} file with:`);
|
|
476
|
-
log(` ${colors.green}STRIPE_KEY=sk_test_your_stripe_secret_key_here${colors.reset}`);
|
|
477
|
-
log(` ${colors.green}STRIPE_ENDPOINT_SECRET=whsec_your_webhook_endpoint_secret_here${colors.reset}`);
|
|
478
|
-
log(` Step by Step Guide: ${colors.blue}https://github.com/stevederico/skateboard#-stripe-setup${colors.reset}`);
|
|
479
|
-
|
|
480
|
-
log(`\n${colors.bold}Get started with:${colors.reset}`, 'yellow');
|
|
481
|
-
log(`\n ${colors.cyan}cd ${projectName}${colors.reset}`);
|
|
482
|
-
log(` ${colors.cyan}npm run start${colors.reset}`);
|
|
483
|
-
log(`\n${colors.yellow}Happy skating! 🛹${colors.reset}\n`);
|
|
484
|
-
|
|
485
588
|
} catch (err) {
|
|
486
|
-
|
|
589
|
+
if (quiet) {
|
|
590
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
591
|
+
} else {
|
|
592
|
+
error(`Failed to create project: ${err.message}`);
|
|
593
|
+
}
|
|
487
594
|
process.exit(1);
|
|
488
595
|
}
|
|
489
596
|
}
|
package/package.json
CHANGED