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.
Files changed (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/cli.js +172 -65
  3. 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
- function log(message, color = 'reset') {
22
+ let log = (message, color = 'reset') => {
23
23
  console.log(`${colors[color]}${message}${colors.reset}`);
24
- }
24
+ };
25
25
 
26
- function error(message) {
26
+ let error = (message) => {
27
27
  log(`❌ ${message}`, 'red');
28
- }
28
+ };
29
29
 
30
- function success(message) {
30
+ let success = (message) => {
31
31
  log(`✅ ${message}`, 'green');
32
- }
32
+ };
33
33
 
34
- function info(message) {
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
- log(`\n${colors.bold}Let's configure your Skateboard app!${colors.reset}\n`);
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 appName = await ask('App display name', projectName.split('-').map(word =>
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 = await askChoice('Choose your app color:', colorChoices);
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 = await askChoice('Choose an app icon:', iconChoices);
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 = await askChoice('Choose your database:', databaseChoices, 0);
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 (selectedDatabase.value === 'postgresql') {
238
- connectionString = await ask('PostgreSQL connection string (optional)', '');
239
- } else if (selectedDatabase.value === 'mongodb') {
240
- connectionString = await ask('MongoDB connection string (optional)', '');
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 Optional project directory name (will prompt if not provided)
366
+ project-name Optional project directory name (will prompt if not provided)
295
367
 
296
368
  ${colors.cyan}Options:${colors.reset}
297
- --help, -h Show this help message
298
- --version, -v Show version number
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 # Interactive mode
302
- npx create-skateboard-app my-app # With project name
303
- npx create-skateboard-app awesome-project # With custom name
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
- // Get project name from command line
314
- const args = process.argv.slice(2);
315
- let projectName = args[0];
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 (args.includes('--help') || args.includes('-h')) {
400
+ if (flags.help) {
319
401
  showHelp();
320
402
  process.exit(0);
321
403
  }
322
404
 
323
- if (args.includes('--version') || args.includes('-v')) {
405
+ if (flags.version) {
324
406
  showVersion();
325
407
  process.exit(0);
326
408
  }
327
409
 
328
- // If no project name provided, ask for it
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
- log(`\n${colors.bold}🛹 Welcome to Skateboard App Creator!${colors.reset}\n`);
331
- projectName = await ask('Project directory name', 'my-skateboard-app');
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
- log(`\n${colors.bold}${colors.green}🎉 Success! Created ${config.appName}${colors.reset}\n`);
461
-
462
- // Database-specific instructions (only if connection string not provided)
463
- if (config.database.value === 'postgresql' && !config.connectionString) {
464
- log(`\n${colors.yellow}📝 PostgreSQL Setup:${colors.reset}`);
465
- log(` Update the ${colors.cyan}backend/.env${colors.reset} file with:`);
466
- log(` ${colors.green}POSTGRES_URL=postgresql://username:password@localhost:5432/dbname${colors.reset}`);
467
- } else if (config.database.value === 'mongodb' && !config.connectionString) {
468
- log(`\n${colors.yellow}📝 MongoDB Setup:${colors.reset}`);
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}MONGODB_URL=mongodb://localhost:27017/dbname${colors.reset}`);
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
- error(`Failed to create project: ${err.message}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-skateboard-app",
3
- "version": "1.1.3",
3
+ "version": "1.2.1",
4
4
  "description": "Create a full-stack React app with authentication, Stripe payments, and database support in seconds",
5
5
  "main": "index.js",
6
6
  "type": "module",