launchpd 0.1.2 → 0.1.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.
package/LICENSE CHANGED
@@ -1,6 +1,10 @@
1
1
  MIT License
2
2
 
3
+ <<<<<<< HEAD
3
4
  Copyright (c) 2026 Launchpd
5
+ =======
6
+ Copyright (c) 2026 Kent Edoloverio
7
+ >>>>>>> 12ada1bf2dcc63674c3b6955e525a51209308d45
4
8
 
5
9
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
10
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -107,4 +107,4 @@ launchpd logout
107
107
 
108
108
  ## License
109
109
 
110
- MIT
110
+ MIT
package/bin/cli.js CHANGED
@@ -21,6 +21,7 @@ program
21
21
  .option('--dry-run', 'Simulate deployment without uploading to R2')
22
22
  .option('--name <subdomain>', 'Use a custom subdomain (optional)')
23
23
  .option('--expires <time>', 'Auto-delete after time (e.g., 30m, 2h, 1d). Minimum: 30m')
24
+ .option('--verbose', 'Show detailed error information')
24
25
  .action(async (folder, options) => {
25
26
  await deploy(folder, options);
26
27
  });
@@ -29,6 +30,8 @@ program
29
30
  .command('list')
30
31
  .description('List your past deployments')
31
32
  .option('--json', 'Output as JSON')
33
+ .option('--local', 'Only show local deployments')
34
+ .option('--verbose', 'Show detailed error information')
32
35
  .action(async (options) => {
33
36
  await list(options);
34
37
  });
@@ -38,6 +41,7 @@ program
38
41
  .description('List all versions for a subdomain')
39
42
  .argument('<subdomain>', 'The subdomain to list versions for')
40
43
  .option('--json', 'Output as JSON')
44
+ .option('--verbose', 'Show detailed error information')
41
45
  .action(async (subdomain, options) => {
42
46
  await versions(subdomain, options);
43
47
  });
@@ -47,6 +51,7 @@ program
47
51
  .description('Rollback a subdomain to a previous version')
48
52
  .argument('<subdomain>', 'The subdomain to rollback')
49
53
  .option('--to <n>', 'Specific version number to rollback to')
54
+ .option('--verbose', 'Show detailed error information')
50
55
  .action(async (subdomain, options) => {
51
56
  await rollback(subdomain, options);
52
57
  });
package/bin/setup.js CHANGED
@@ -1,62 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { config, validateConfig } from '../src/config.js';
4
- import { info, success, error } from '../src/utils/logger.js';
3
+ import { config } from '../src/config.js';
4
+ import { info, success } from '../src/utils/logger.js';
5
5
  import chalk from 'chalk';
6
6
 
7
7
  /**
8
- * Setup script to validate and display configuration status
8
+ * Setup script to display CLI information
9
9
  */
10
10
  async function setup() {
11
11
  console.log('\n' + chalk.bold.blue('═══════════════════════════════════════'));
12
- console.log(chalk.bold.blue(' Launchpd Configuration Check'));
12
+ console.log(chalk.bold.blue(' Launchpd CLI'));
13
13
  console.log(chalk.bold.blue('═══════════════════════════════════════\n'));
14
14
 
15
- // Check environment variables
16
- info('Checking configuration...\n');
15
+ info('Launchpd is ready to use!\n');
17
16
 
18
- const validation = validateConfig();
19
-
20
- if (!validation.valid) {
21
- error(`Missing required environment variables:`);
22
- for (const missing of validation.missing) {
23
- console.log(chalk.red(` ✗ ${missing}`));
24
- }
25
- console.log('\n' + chalk.yellow('Setup Instructions:'));
26
- console.log(' 1. Copy .env.example to .env:');
27
- console.log(chalk.gray(' cp .env.example .env'));
28
- console.log(' 2. Edit .env and add your Cloudflare R2 credentials');
29
- console.log(' 3. Get credentials from: https://dash.cloudflare.com → R2 → API Tokens\n');
30
- process.exit(1);
31
- }
32
-
33
- // Display current configuration
34
- console.log(chalk.green('✓ All required environment variables set\n'));
35
-
36
- console.log(chalk.bold('Current Configuration:'));
17
+ console.log(chalk.bold('Configuration:'));
37
18
  console.log(chalk.gray('─'.repeat(50)));
38
19
  console.log(chalk.cyan(' Domain: '), config.domain);
39
- console.log(chalk.cyan(' R2 Bucket: '), config.r2.bucketName);
40
- console.log(chalk.cyan(' Account ID: '), config.r2.accountId ? '✓ Set' : '✗ Missing');
41
- console.log(chalk.cyan(' Access Key: '), config.r2.accessKeyId ? '✓ Set' : '✗ Missing');
42
- console.log(chalk.cyan(' Secret Key: '), config.r2.secretAccessKey ? '✓ Set' : '✗ Missing');
20
+ console.log(chalk.cyan(' API: '), config.apiUrl);
21
+ console.log(chalk.cyan(' Version: '), config.version);
43
22
  console.log(chalk.gray('─'.repeat(50)) + '\n');
44
23
 
45
- // Next steps
46
- console.log(chalk.bold('Next Steps:'));
47
- console.log(chalk.gray(' 1. Test dry-run deployment:'));
48
- console.log(chalk.cyan(' npm run dev'));
49
- console.log(chalk.gray(' 2. Deploy Worker:'));
50
- console.log(chalk.cyan(' cd worker && wrangler deploy'));
51
- console.log(chalk.gray(' 3. Verify DNS settings in Cloudflare Dashboard:'));
52
- console.log(chalk.cyan(` DNS → Records → Add A record * → 192.0.2.1 (Proxied)`));
53
- console.log(chalk.gray(' 4. Deploy your first site:'));
24
+ console.log(chalk.bold('Quick Start:'));
25
+ console.log(chalk.gray(' Deploy your first site:'));
54
26
  console.log(chalk.cyan(' launchpd deploy ./your-folder\n'));
55
27
 
56
- success('Setup validation complete!');
28
+ console.log(chalk.gray(' Login for more quota:'));
29
+ console.log(chalk.cyan(' launchpd login\n'));
30
+
31
+ console.log(chalk.gray(' List your deployments:'));
32
+ console.log(chalk.cyan(' launchpd list\n'));
33
+
34
+ success('No configuration needed - just deploy!');
57
35
  }
58
36
 
59
37
  setup().catch(err => {
60
- error(`Setup failed: ${err.message}`);
38
+ console.error(`Setup failed: ${err.message}`);
61
39
  process.exit(1);
62
40
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchpd",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Deploy static sites instantly to a live URL",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,6 @@
21
21
  "scripts": {
22
22
  "start": "node bin/cli.js",
23
23
  "dev": "node bin/cli.js deploy ../examples/test-site --dry-run",
24
- "setup": "node bin/setup.js",
25
24
  "test": "vitest run",
26
25
  "test:watch": "vitest",
27
26
  "test:coverage": "vitest run --coverage",
@@ -35,7 +34,6 @@
35
34
  "deploy",
36
35
  "cli",
37
36
  "cloudflare",
38
- "r2",
39
37
  "cdn",
40
38
  "website",
41
39
  "publish"
@@ -52,12 +50,11 @@
52
50
  },
53
51
  "homepage": "https://launchpd.cloud",
54
52
  "dependencies": {
55
- "@aws-sdk/client-s3": "^3.700.0",
56
53
  "chalk": "^5.4.0",
57
54
  "commander": "^14.0.0",
58
- "dotenv": "^16.4.0",
59
55
  "mime-types": "^2.1.35",
60
- "nanoid": "^5.1.0"
56
+ "nanoid": "^5.1.0",
57
+ "ora": "^8.0.1"
61
58
  },
62
59
  "devDependencies": {
63
60
  "@eslint/js": "^9.39.2",
@@ -7,7 +7,8 @@ import { createInterface } from 'node:readline';
7
7
  import { exec } from 'node:child_process';
8
8
  import { config } from '../config.js';
9
9
  import { getCredentials, saveCredentials, clearCredentials, isLoggedIn } from '../utils/credentials.js';
10
- import { success, error, info, warning } from '../utils/logger.js';
10
+ import { success, error, errorWithSuggestions, info, warning, spinner } from '../utils/logger.js';
11
+ import chalk from 'chalk';
11
12
 
12
13
  const API_BASE_URL = `https://api.${config.domain}`;
13
14
  const REGISTER_URL = `https://portal.${config.domain}/auth/register`;
@@ -49,8 +50,7 @@ async function validateApiKey(apiKey) {
49
50
  return data;
50
51
  }
51
52
  return null;
52
- } catch (err) {
53
- error(`Failed to validate API key: ${err.message}`);
53
+ } catch {
54
54
  return null;
55
55
  }
56
56
  }
@@ -62,29 +62,37 @@ export async function login() {
62
62
  // Check if already logged in
63
63
  if (await isLoggedIn()) {
64
64
  const creds = await getCredentials();
65
- warning(`Already logged in as ${creds.email || creds.userId}`);
65
+ warning(`Already logged in as ${chalk.cyan(creds.email || creds.userId)}`);
66
66
  info('Run "launchpd logout" to switch accounts');
67
67
  return;
68
68
  }
69
69
 
70
- console.log('\n🔐 Launchpd Login\n');
70
+ console.log('\nLaunchpd Login\n');
71
71
  console.log('Enter your API key from the dashboard.');
72
- console.log(`Don't have one? Run "launchpd register" first.\n`);
72
+ console.log(`Don't have one? Run ${chalk.cyan('"launchpd register"')} first.\n`);
73
73
 
74
74
  const apiKey = await prompt('API Key: ');
75
75
 
76
76
  if (!apiKey) {
77
- error('API key is required');
77
+ errorWithSuggestions('API key is required', [
78
+ 'Get your API key from the dashboard',
79
+ `Visit: https://portal.${config.domain}/api-keys`,
80
+ 'Run "launchpd register" if you don\'t have an account',
81
+ ]);
78
82
  process.exit(1);
79
83
  }
80
84
 
81
- info('Validating API key...');
85
+ const validateSpinner = spinner('Validating API key...');
82
86
 
83
87
  const result = await validateApiKey(apiKey);
84
88
 
85
89
  if (!result) {
86
- error('Invalid API key. Please check and try again.');
87
- console.log(`\nGet your API key at: https://portal.${config.domain}/api-keys`);
90
+ validateSpinner.fail('Invalid API key');
91
+ errorWithSuggestions('Please check and try again.', [
92
+ `Get your API key at: https://portal.${config.domain}/api-keys`,
93
+ 'Make sure you copied the full key',
94
+ 'API keys start with "lpd_"',
95
+ ]);
88
96
  process.exit(1);
89
97
  }
90
98
 
@@ -96,11 +104,13 @@ export async function login() {
96
104
  tier: result.tier,
97
105
  });
98
106
 
99
- success(`Logged in successfully!`);
100
- console.log(`\n Email: ${result.user?.email || 'N/A'}`);
101
- console.log(` Tier: ${result.tier}`);
102
- console.log(` Sites: ${result.usage?.siteCount || 0}/${result.limits?.maxSites || '?'}`);
103
- console.log(` Storage: ${result.usage?.storageUsedMB || 0}MB/${result.limits?.maxStorageMB || '?'}MB\n`);
107
+ validateSpinner.succeed('Logged in successfully!');
108
+ console.log('');
109
+ console.log(` ${chalk.gray('Email:')} ${chalk.cyan(result.user?.email || 'N/A')}`);
110
+ console.log(` ${chalk.gray('Tier:')} ${chalk.green(result.tier)}`);
111
+ console.log(` ${chalk.gray('Sites:')} ${result.usage?.siteCount || 0}/${result.limits?.maxSites || '?'}`);
112
+ console.log(` ${chalk.gray('Storage:')} ${result.usage?.storageUsedMB || 0}MB/${result.limits?.maxStorageMB || '?'}MB`);
113
+ console.log('');
104
114
  }
105
115
 
106
116
  /**
@@ -117,19 +127,19 @@ export async function logout() {
117
127
  const creds = await getCredentials();
118
128
  await clearCredentials();
119
129
 
120
- success(`Logged out successfully`);
130
+ success('Logged out successfully');
121
131
  if (creds?.email) {
122
- info(`Was logged in as: ${creds.email}`);
132
+ info(`Was logged in as: ${chalk.cyan(creds.email)}`);
123
133
  }
124
- console.log(`\nYou can still deploy anonymously (limited to 3 sites, 50MB).`);
134
+ console.log(`\nYou can still deploy anonymously (limited to ${chalk.yellow('3 sites')}, ${chalk.yellow('50MB')}).`);
125
135
  }
126
136
 
127
137
  /**
128
138
  * Register command - opens browser to registration page
129
139
  */
130
140
  export async function register() {
131
- console.log('\n🚀 Register for Launchpd\n');
132
- console.log(`Opening registration page: ${REGISTER_URL}\n`);
141
+ console.log('\nRegister for Launchpd\n');
142
+ console.log(`Opening registration page: ${chalk.cyan(REGISTER_URL)}\n`);
133
143
 
134
144
  // Open browser based on platform
135
145
  const platform = process.platform;
@@ -145,20 +155,20 @@ export async function register() {
145
155
 
146
156
  exec(cmd, (err) => {
147
157
  if (err) {
148
- console.log(`Please open this URL in your browser:\n ${REGISTER_URL}\n`);
158
+ console.log(`Please open this URL in your browser:\n ${chalk.cyan(REGISTER_URL)}\n`);
149
159
  }
150
160
  });
151
161
 
152
162
  console.log('After registering:');
153
- console.log(' 1. Get your API key from the dashboard');
154
- console.log(' 2. Run: launchpd login');
163
+ console.log(` 1. Get your API key from the dashboard`);
164
+ console.log(` 2. Run: ${chalk.cyan('launchpd login')}`);
155
165
  console.log('');
156
166
 
157
167
  info('Registration benefits:');
158
- console.log(' ✓ 10 sites (instead of 3)');
159
- console.log(' ✓ 100MB storage (instead of 50MB)');
160
- console.log(' ✓ 30-day retention (instead of 7 days)');
161
- console.log(' ✓ 10 versions per site');
168
+ console.log(` ${chalk.green('✓')} ${chalk.white('10 sites')} ${chalk.gray('(instead of 3)')}`);
169
+ console.log(` ${chalk.green('✓')} ${chalk.white('100MB storage')} ${chalk.gray('(instead of 50MB)')}`);
170
+ console.log(` ${chalk.green('✓')} ${chalk.white('30-day retention')} ${chalk.gray('(instead of 7 days)')}`);
171
+ console.log(` ${chalk.green('✓')} ${chalk.white('10 versions per site')}`);
162
172
  console.log('');
163
173
  }
164
174
 
@@ -171,12 +181,12 @@ export async function whoami() {
171
181
  if (!creds) {
172
182
  console.log('\n👤 Not logged in (anonymous mode)\n');
173
183
  console.log('Anonymous limits:');
174
- console.log(' • 3 sites maximum');
175
- console.log(' • 50MB total storage');
176
- console.log(' • 7-day retention');
177
- console.log(' • 1 version per site');
178
- console.log(`\nRun "launchpd login" to authenticate`);
179
- console.log(`Run "launchpd register" to create an account\n`);
184
+ console.log(`${chalk.white('3 sites')} maximum`);
185
+ console.log(`${chalk.white('50MB')} total storage`);
186
+ console.log(`${chalk.white('7-day')} retention`);
187
+ console.log(`${chalk.white('1 version')} per site`);
188
+ console.log(`\nRun ${chalk.cyan('"launchpd login"')} to authenticate`);
189
+ console.log(`Run ${chalk.cyan('"launchpd register"')} to create an account\n`);
180
190
  return;
181
191
  }
182
192
 
@@ -192,7 +202,7 @@ export async function whoami() {
192
202
  process.exit(1);
193
203
  }
194
204
 
195
- console.log(`\n👤 Logged in as: ${result.user?.email || result.user?.id}\n`);
205
+ console.log(`\nLogged in as: ${result.user?.email || result.user?.id}\n`);
196
206
 
197
207
  console.log('Account Info:');
198
208
  console.log(` User ID: ${result.user?.id}`);
@@ -232,38 +242,43 @@ export async function quota() {
232
242
  const creds = await getCredentials();
233
243
 
234
244
  if (!creds) {
235
- console.log('\n📊 Anonymous Quota Status\n');
236
- console.log('You are not logged in.');
245
+ console.log(`\n${chalk.bold('Anonymous Quota Status')}\n`);
246
+ console.log(chalk.gray('You are not logged in.'));
237
247
  console.log('');
238
- console.log('Anonymous tier limits:');
239
- console.log(' ┌─────────────────────────────────┐');
240
- console.log(' │ Sites: 3 maximum │');
241
- console.log(' │ Storage: 50MB total │');
242
- console.log(' │ Retention: 7 days │');
243
- console.log(' │ Versions: 1 per site │');
244
- console.log(' └─────────────────────────────────┘');
248
+ console.log(chalk.bold('Anonymous tier limits:'));
249
+ console.log(chalk.gray(' ┌─────────────────────────────────┐'));
250
+ console.log(chalk.gray(' │') + ` Sites: ${chalk.white('3 maximum')} ` + chalk.gray('│'));
251
+ console.log(chalk.gray(' │') + ` Storage: ${chalk.white('50MB total')} ` + chalk.gray('│'));
252
+ console.log(chalk.gray(' │') + ` Retention: ${chalk.white('7 days')} ` + chalk.gray('│'));
253
+ console.log(chalk.gray(' │') + ` Versions: ${chalk.white('1 per site')} ` + chalk.gray('│'));
254
+ console.log(chalk.gray(' └─────────────────────────────────┘'));
245
255
  console.log('');
246
- console.log('💡 Register for FREE to unlock more:');
247
- console.log(' → 10 sites');
248
- console.log(' → 100MB storage');
249
- console.log(' → 30-day retention');
250
- console.log(' → 10 versions per site');
256
+ console.log(`${chalk.cyan('Register for FREE')} to unlock more:`);
257
+ console.log(` ${chalk.green('→')} ${chalk.white('10 sites')}`);
258
+ console.log(` ${chalk.green('→')} ${chalk.white('100MB storage')}`);
259
+ console.log(` ${chalk.green('→')} ${chalk.white('30-day retention')}`);
260
+ console.log(` ${chalk.green('→')} ${chalk.white('10 versions per site')}`);
251
261
  console.log('');
252
- console.log('Run: launchpd register');
262
+ console.log(`Run: ${chalk.cyan('launchpd register')}`);
253
263
  console.log('');
254
264
  return;
255
265
  }
256
266
 
257
- info('Fetching quota status...');
267
+ const fetchSpinner = spinner('Fetching quota status...');
258
268
 
259
269
  const result = await validateApiKey(creds.apiKey);
260
270
 
261
271
  if (!result) {
262
- error('Failed to fetch quota. API key may be invalid.');
272
+ fetchSpinner.fail('Failed to fetch quota');
273
+ errorWithSuggestions('API key may be invalid.', [
274
+ 'Run "launchpd login" to re-authenticate',
275
+ 'Check your internet connection',
276
+ ]);
263
277
  process.exit(1);
264
278
  }
265
279
 
266
- console.log(`\n📊 Quota Status for: ${result.user?.email || creds.email}\n`);
280
+ fetchSpinner.succeed('Quota fetched');
281
+ console.log(`\n${chalk.bold('Quota Status for:')} ${chalk.cyan(result.user?.email || creds.email)}\n`);
267
282
 
268
283
  // Sites usage
269
284
  const sitesUsed = result.usage?.siteCount || 0;
@@ -271,7 +286,7 @@ export async function quota() {
271
286
  const sitesPercent = Math.round((sitesUsed / sitesMax) * 100);
272
287
  const sitesBar = createProgressBar(sitesUsed, sitesMax);
273
288
 
274
- console.log(`Sites: ${sitesBar} ${sitesUsed}/${sitesMax} (${sitesPercent}%)`);
289
+ console.log(`${chalk.gray('Sites:')} ${sitesBar} ${chalk.white(sitesUsed)}/${sitesMax} (${getPercentColor(sitesPercent)})`);
275
290
 
276
291
  // Storage usage
277
292
  const storageMB = result.usage?.storageUsedMB || 0;
@@ -279,46 +294,61 @@ export async function quota() {
279
294
  const storagePercent = Math.round((storageMB / storageMaxMB) * 100);
280
295
  const storageBar = createProgressBar(storageMB, storageMaxMB);
281
296
 
282
- console.log(`Storage: ${storageBar} ${storageMB}MB/${storageMaxMB}MB (${storagePercent}%)`);
297
+ console.log(`${chalk.gray('Storage:')} ${storageBar} ${chalk.white(storageMB)}MB/${storageMaxMB}MB (${getPercentColor(storagePercent)})`);
283
298
 
284
299
  console.log('');
285
- console.log(`Tier: ${result.tier || 'free'}`);
286
- console.log(`Retention: ${result.limits?.retentionDays || 30} days`);
287
- console.log(`Max versions: ${result.limits?.maxVersionsPerSite || 10} per site`);
300
+ console.log(`${chalk.gray('Tier:')} ${chalk.green(result.tier || 'free')}`);
301
+ console.log(`${chalk.gray('Retention:')} ${chalk.white(result.limits?.retentionDays || 30)} days`);
302
+ console.log(`${chalk.gray('Max versions:')} ${chalk.white(result.limits?.maxVersionsPerSite || 10)} per site`);
288
303
  console.log('');
289
304
 
290
305
  // Status indicators
291
306
  if (result.canCreateNewSite === false) {
292
- warning('⚠️ Site limit reached - cannot create new sites');
307
+ warning('Site limit reached - cannot create new sites');
293
308
  }
294
309
 
295
310
  if (storagePercent > 80) {
296
- warning(`⚠️ Storage ${storagePercent}% used - consider cleaning up old deployments`);
311
+ warning(`Storage ${storagePercent}% used - consider cleaning up old deployments`);
297
312
  }
298
313
 
299
314
  if (result.tier === 'free') {
300
315
  console.log('');
301
- info('💎 Upgrade to Pro for 50 sites, 1GB storage, and 50 versions');
316
+ info(`Upgrade to ${chalk.magenta('Pro')} for 50 sites, 1GB storage, and 50 versions`);
302
317
  }
303
318
  console.log('');
304
319
  }
305
320
 
306
321
  /**
307
- * Create a simple progress bar
322
+ * Create a simple progress bar with color coding
308
323
  */
309
324
  function createProgressBar(current, max, width = 20) {
310
325
  const filled = Math.round((current / max) * width);
311
326
  const empty = width - filled;
312
327
  const percent = (current / max) * 100;
313
328
 
314
- let bar = '';
329
+ const filledChar = '';
330
+ let barColor;
331
+
315
332
  if (percent >= 90) {
316
- bar = '█'.repeat(filled) + '░'.repeat(empty);
333
+ barColor = chalk.red;
317
334
  } else if (percent >= 70) {
318
- bar = '█'.repeat(filled) + '░'.repeat(empty);
335
+ barColor = chalk.yellow;
319
336
  } else {
320
- bar = '█'.repeat(filled) + '░'.repeat(empty);
337
+ barColor = chalk.green;
321
338
  }
322
339
 
340
+ const bar = barColor(filledChar.repeat(filled)) + chalk.gray('░'.repeat(empty));
323
341
  return `[${bar}]`;
324
342
  }
343
+
344
+ /**
345
+ * Get colored percentage text
346
+ */
347
+ function getPercentColor(percent) {
348
+ if (percent >= 90) {
349
+ return chalk.red(`${percent}%`);
350
+ } else if (percent >= 70) {
351
+ return chalk.yellow(`${percent}%`);
352
+ }
353
+ return chalk.green(`${percent}%`);
354
+ }