launchpd 1.0.3 → 1.0.6

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.
@@ -2,10 +2,17 @@
2
2
  * Commands index - exports all CLI commands
3
3
  */
4
4
 
5
- export { deploy } from './deploy.js';
6
- export { list } from './list.js';
7
- export { rollback } from './rollback.js';
8
- export { versions } from './versions.js';
9
- export { init } from './init.js';
10
- export { status } from './status.js';
11
- export { login, logout, register, whoami, quota, resendEmailVerification } from './auth.js';
5
+ export { deploy } from './deploy.js'
6
+ export { list } from './list.js'
7
+ export { rollback } from './rollback.js'
8
+ export { versions } from './versions.js'
9
+ export { init } from './init.js'
10
+ export { status } from './status.js'
11
+ export {
12
+ login,
13
+ logout,
14
+ register,
15
+ whoami,
16
+ quota,
17
+ resendEmailVerification
18
+ } from './auth.js'
@@ -1,90 +1,113 @@
1
- import { initProjectConfig, findProjectRoot, getProjectConfig, saveProjectConfig } from '../utils/projectConfig.js';
2
- import { checkSubdomainAvailable, reserveSubdomain, listSubdomains } from '../utils/api.js';
3
- import { isLoggedIn } from '../utils/credentials.js';
4
- import { success, errorWithSuggestions, info, spinner, warning } from '../utils/logger.js';
5
- import { prompt } from '../utils/prompt.js';
6
- import chalk from 'chalk';
1
+ import {
2
+ initProjectConfig,
3
+ findProjectRoot,
4
+ getProjectConfig,
5
+ saveProjectConfig
6
+ } from '../utils/projectConfig.js'
7
+ import {
8
+ checkSubdomainAvailable,
9
+ reserveSubdomain,
10
+ listSubdomains
11
+ } from '../utils/api.js'
12
+ import { isLoggedIn } from '../utils/credentials.js'
13
+ import {
14
+ success,
15
+ errorWithSuggestions,
16
+ info,
17
+ spinner,
18
+ warning
19
+ } from '../utils/logger.js'
20
+ import { prompt } from '../utils/prompt.js'
21
+ import chalk from 'chalk'
7
22
 
8
23
  /**
9
24
  * Initialize a new project in the current directory
10
25
  * @param {object} options - Command options
11
26
  * @param {string} options.name - Optional subdomain name
12
27
  */
13
- export async function init(options) {
14
- const projectRoot = findProjectRoot();
15
- if (projectRoot) {
16
- const config = await getProjectConfig(projectRoot);
17
- warning(`This directory is already part of a Launchpd project linked to: ${chalk.bold(config?.subdomain)}`);
28
+ export async function init (options) {
29
+ const projectRoot = findProjectRoot()
30
+ if (projectRoot) {
31
+ const config = await getProjectConfig(projectRoot)
32
+ warning(
33
+ `This directory is already part of a Launchpd project linked to: ${chalk.bold(config?.subdomain)}`
34
+ )
18
35
 
19
- const confirm = await prompt('Would you like to re-link this project to a different subdomain? (y/N): ');
20
- if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') {
21
- return;
22
- }
36
+ const confirm = await prompt(
37
+ 'Would you like to re-link this project to a different subdomain? (y/N): '
38
+ )
39
+ if (confirm.toLowerCase() !== 'y' && confirm.toLowerCase() !== 'yes') {
40
+ return
23
41
  }
42
+ }
24
43
 
25
- if (!await isLoggedIn()) {
26
- errorWithSuggestions('You must be logged in to initialize a project.', [
27
- 'Run "launchpd login" to log in',
28
- 'Run "launchpd register" to create an account'
29
- ]);
30
- return;
31
- }
44
+ if (!(await isLoggedIn())) {
45
+ errorWithSuggestions('You must be logged in to initialize a project.', [
46
+ 'Run "launchpd login" to log in',
47
+ 'Run "launchpd register" to create an account'
48
+ ])
49
+ return
50
+ }
32
51
 
33
- let subdomain = options.name;
52
+ let subdomain = options.name
34
53
 
35
- if (!subdomain) {
36
- info('Linking this directory to a Launchpd subdomain...');
37
- subdomain = await prompt('Enter subdomain name (e.g. my-awesome-site): ');
38
- }
54
+ if (!subdomain) {
55
+ info('Linking this directory to a Launchpd subdomain...')
56
+ subdomain = await prompt('Enter subdomain name (e.g. my-awesome-site): ')
57
+ }
39
58
 
40
- if (!subdomain || !/^[a-z0-9-]+$/.test(subdomain)) {
41
- errorWithSuggestions('Invalid subdomain. Use lowercase alphanumeric and hyphens only.', [
42
- 'Example: my-site-123',
43
- 'No spaces or special characters'
44
- ]);
45
- return;
46
- }
59
+ if (!subdomain || !/^[a-z0-9-]+$/.test(subdomain)) {
60
+ errorWithSuggestions(
61
+ 'Invalid subdomain. Use lowercase alphanumeric and hyphens only.',
62
+ ['Example: my-site-123', 'No spaces or special characters']
63
+ )
64
+ return
65
+ }
47
66
 
48
- const checkSpinner = spinner(`Checking if "${subdomain}" is available...`);
49
- try {
50
- const isAvailable = await checkSubdomainAvailable(subdomain);
51
- let owned = false;
67
+ const checkSpinner = spinner(`Checking if "${subdomain}" is available...`)
68
+ try {
69
+ const isAvailable = await checkSubdomainAvailable(subdomain)
70
+ let owned = false
52
71
 
53
- if (!isAvailable) {
54
- // Check if user owns it
55
- const apiResult = await listSubdomains();
56
- const ownedSubdomains = apiResult?.subdomains || [];
57
- owned = ownedSubdomains.some(s => s.subdomain === subdomain);
72
+ if (!isAvailable) {
73
+ // Check if user owns it
74
+ const apiResult = await listSubdomains()
75
+ const ownedSubdomains = apiResult?.subdomains || []
76
+ owned = ownedSubdomains.some((s) => {
77
+ return s.subdomain === subdomain
78
+ })
58
79
 
59
- if (!owned) {
60
- checkSpinner.fail(`Subdomain "${subdomain}" is already taken.`);
61
- return;
62
- }
63
- checkSpinner.info(`Subdomain "${subdomain}" is already yours.`);
64
- } else {
65
- checkSpinner.succeed(`Subdomain "${subdomain}" is available!`);
66
- }
80
+ if (!owned) {
81
+ checkSpinner.fail(`Subdomain "${subdomain}" is already taken.`)
82
+ return
83
+ }
84
+ checkSpinner.info(`Subdomain "${subdomain}" is already yours.`)
85
+ } else {
86
+ checkSpinner.succeed(`Subdomain "${subdomain}" is available!`)
87
+ }
67
88
 
68
- const reserveStatus = owned ? true : await reserveSubdomain(subdomain);
69
- if (reserveStatus) {
70
- if (projectRoot) {
71
- // Re-link
72
- const config = await getProjectConfig(projectRoot);
73
- config.subdomain = subdomain;
74
- config.updatedAt = new Date().toISOString();
75
- await saveProjectConfig(config, projectRoot);
76
- success(`Project re-linked! New subdomain: ${subdomain}.launchpd.cloud`);
77
- } else {
78
- await initProjectConfig(subdomain);
79
- success(`Project initialized! Linked to: ${subdomain}.launchpd.cloud`);
80
- }
81
- info('Now you can run "launchpd deploy" without specifying a name.');
82
- }
83
- } catch (err) {
84
- checkSpinner.fail('Failed to initialize project');
85
- errorWithSuggestions(err.message, [
86
- 'Check your internet connection',
87
- 'Try a different subdomain name'
88
- ]);
89
+ const reserveStatus = owned ? true : await reserveSubdomain(subdomain)
90
+ if (reserveStatus) {
91
+ if (projectRoot) {
92
+ // Re-link
93
+ const config = await getProjectConfig(projectRoot)
94
+ config.subdomain = subdomain
95
+ config.updatedAt = new Date().toISOString()
96
+ await saveProjectConfig(config, projectRoot)
97
+ success(
98
+ `Project re-linked! New subdomain: ${subdomain}.launchpd.cloud`
99
+ )
100
+ } else {
101
+ await initProjectConfig(subdomain)
102
+ success(`Project initialized! Linked to: ${subdomain}.launchpd.cloud`)
103
+ }
104
+ info('Now you can run "launchpd deploy" without specifying a name.')
89
105
  }
106
+ } catch (err) {
107
+ checkSpinner.fail('Failed to initialize project')
108
+ errorWithSuggestions(err.message, [
109
+ 'Check your internet connection',
110
+ 'Try a different subdomain name'
111
+ ])
112
+ }
90
113
  }
@@ -1,8 +1,14 @@
1
- import { getLocalDeployments } from '../utils/localConfig.js';
2
- import { listDeployments as listFromAPI } from '../utils/api.js';
3
- import { errorWithSuggestions, info, spinner, formatSize, log } from '../utils/logger.js';
4
- import { formatTimeRemaining, isExpired } from '../utils/expiration.js';
5
- import chalk from 'chalk';
1
+ import { getLocalDeployments } from '../utils/localConfig.js'
2
+ import { listDeployments as listFromAPI } from '../utils/api.js'
3
+ import {
4
+ errorWithSuggestions,
5
+ info,
6
+ spinner,
7
+ formatSize,
8
+ log
9
+ } from '../utils/logger.js'
10
+ import { formatTimeRemaining, isExpired } from '../utils/expiration.js'
11
+ import chalk from 'chalk'
6
12
 
7
13
  /**
8
14
  * List all deployments (from API or local storage)
@@ -11,127 +17,119 @@ import chalk from 'chalk';
11
17
  * @param {boolean} options.local - Only show local deployments
12
18
  * @param {boolean} options.verbose - Show verbose error details
13
19
  */
14
- export async function list(options) {
15
- const verbose = options.verbose || false;
16
-
17
- try {
18
- let deployments = [];
19
- let source = 'local';
20
-
21
- const fetchSpinner = spinner('Fetching deployments...');
22
-
23
- // Try API first unless --local flag is set
24
- if (!options.local) {
25
- const apiResult = await listFromAPI();
26
- if (apiResult && apiResult.deployments) {
27
- deployments = apiResult.deployments.map(d => ({
28
- subdomain: d.subdomain,
29
- folderName: d.folder_name,
30
- fileCount: d.file_count,
31
- totalBytes: d.total_bytes,
32
- version: d.version,
33
- timestamp: d.created_at,
34
- expiresAt: d.expires_at,
35
- message: d.message,
36
- isActive: d.active_version === d.version,
37
- }));
38
- source = 'api';
39
- }
40
- }
41
-
42
- // Fallback to local storage if API unavailable
43
- if (deployments.length === 0) {
44
- deployments = await getLocalDeployments();
45
- source = 'local';
46
- }
47
-
48
- if (deployments.length === 0) {
49
- fetchSpinner.warn('No deployments found');
50
- info('Deploy a folder with: ' + chalk.cyan('launchpd deploy ./my-folder'));
51
- return;
52
- }
53
-
54
- fetchSpinner.succeed(`Found ${deployments.length} deployment(s)`);
55
-
56
- if (options.json) {
57
- log(JSON.stringify(deployments, null, 2));
58
- return;
59
- }
60
-
61
- // Display as table
62
- log('');
63
- log(chalk.bold('Your Deployments:'));
64
- log(chalk.gray('─'.repeat(100)));
65
-
66
- // Header
67
- log(
68
- chalk.gray(
69
- padRight('URL', 35) +
70
- padRight('VER', 6) +
71
- padRight('FOLDER', 15) +
72
- padRight('FILES', 7) +
73
- padRight('SIZE', 10) +
74
- padRight('DATE', 12) +
75
- 'STATUS'
76
- )
77
- );
78
- log(chalk.gray('─'.repeat(100)));
79
-
80
- // Rows (most recent first)
81
- const sorted = [...deployments].reverse();
82
- for (const dep of sorted) {
83
- const url = `https://${dep.subdomain}.launchpd.cloud`;
84
- const date = new Date(dep.timestamp).toLocaleDateString();
85
- const size = dep.totalBytes ? formatSize(dep.totalBytes) : '-';
86
-
87
- // Determine status with colors
88
- let status;
89
- if (dep.expiresAt && isExpired(dep.expiresAt)) {
90
- status = chalk.red.bold('● expired');
91
- } else if (dep.isActive) {
92
- status = chalk.green.bold('● active');
93
- } else if (dep.expiresAt) {
94
- status = chalk.yellow(`⏱ ${formatTimeRemaining(dep.expiresAt)}`);
95
- } else {
96
- status = chalk.gray('○ inactive');
97
- }
98
-
99
- // Version info
100
- const versionStr = `v${dep.version || 1}`;
101
-
102
- log(
103
- chalk.cyan(padRight(url, 35)) +
104
- chalk.magenta(padRight(versionStr, 6)) +
105
- chalk.white(padRight(dep.folderName || '-', 15)) +
106
- chalk.white(padRight(String(dep.fileCount), 7)) +
107
- chalk.white(padRight(size, 10)) +
108
- chalk.gray(padRight(date, 12)) +
109
- status +
110
- (dep.message ? chalk.gray(` - ${dep.message}`) : '')
111
- );
112
- }
113
-
114
- log(chalk.gray('─'.repeat(100)));
115
- const syncStatus = source === 'api'
116
- ? chalk.green(' ✓ synced')
117
- : chalk.yellow(' ⚠ local only');
118
- log(chalk.gray(`Total: ${deployments.length} deployment(s)`) + syncStatus);
119
- log('');
120
-
121
- } catch (err) {
122
- errorWithSuggestions(`Failed to list deployments: ${err.message}`, [
123
- 'Check your internet connection',
124
- 'Use --local flag to show local deployments only',
125
- 'Try running with --verbose for more details',
126
- ], { verbose, cause: err });
127
- process.exit(1);
20
+ export async function list (options) {
21
+ const verbose = options.verbose || false
22
+
23
+ try {
24
+ let deployments = []
25
+ let source = 'local'
26
+
27
+ const fetchSpinner = spinner('Fetching deployments...')
28
+
29
+ // Try API first unless --local flag is set
30
+ if (!options.local) {
31
+ const apiResult = await listFromAPI()
32
+ if (apiResult?.deployments) {
33
+ deployments = apiResult.deployments.map((d) => ({
34
+ subdomain: d.subdomain,
35
+ folderName: d.folder_name,
36
+ fileCount: d.file_count,
37
+ totalBytes: d.total_bytes,
38
+ version: d.version,
39
+ timestamp: d.created_at,
40
+ expiresAt: d.expires_at,
41
+ message: d.message,
42
+ isActive: d.active_version === d.version
43
+ }))
44
+ source = 'api'
45
+ }
128
46
  }
47
+
48
+ // Fallback to local storage if API unavailable
49
+ if (deployments.length === 0) {
50
+ deployments = await getLocalDeployments()
51
+ source = 'local'
52
+ }
53
+
54
+ if (deployments.length === 0) {
55
+ fetchSpinner.warn('No deployments found')
56
+ info(
57
+ `Deploy a folder with: ${chalk.cyan('launchpd deploy ./my-folder')}`
58
+ )
59
+ return
60
+ }
61
+
62
+ fetchSpinner.succeed(`Found ${deployments.length} deployment(s)`)
63
+
64
+ if (options.json) {
65
+ log(JSON.stringify(deployments, null, 2))
66
+ return
67
+ }
68
+
69
+ // Display as table
70
+ log('')
71
+ log(chalk.bold('Your Deployments:'))
72
+ log(chalk.gray('─'.repeat(100)))
73
+
74
+ // Header
75
+ log(
76
+ chalk.gray(
77
+ `${padRight('URL', 35)}${padRight('VER', 6)}${padRight('FOLDER', 15)}${padRight('FILES', 7)}${padRight('SIZE', 10)}${padRight('DATE', 12)}STATUS`
78
+ )
79
+ )
80
+ log(chalk.gray('─'.repeat(100)))
81
+
82
+ // Rows (most recent first)
83
+ const sorted = [...deployments].reverse()
84
+ for (const dep of sorted) {
85
+ const url = `https://${dep.subdomain}.launchpd.cloud`
86
+ const date = new Date(dep.timestamp).toLocaleDateString()
87
+ const size = dep.totalBytes ? formatSize(dep.totalBytes) : '-'
88
+
89
+ // Determine status with colors
90
+ let status = chalk.gray('○ inactive')
91
+ if (dep.expiresAt && isExpired(dep.expiresAt)) {
92
+ status = chalk.red.bold('● expired')
93
+ } else if (dep.isActive) {
94
+ status = chalk.green.bold('● active')
95
+ } else if (dep.expiresAt) {
96
+ status = chalk.yellow(`⏱ ${formatTimeRemaining(dep.expiresAt)}`)
97
+ }
98
+
99
+ // Version info
100
+ const versionStr = `v${dep.version || 1}`
101
+
102
+ const messageSuffix = dep.message ? ` - ${dep.message}` : ''
103
+ log(
104
+ `${chalk.cyan(padRight(url, 35))}${chalk.magenta(padRight(versionStr, 6))}${chalk.white(padRight(dep.folderName || '-', 15))}${chalk.white(padRight(String(dep.fileCount), 7))}${chalk.white(padRight(size, 10))}${chalk.gray(padRight(date, 12))}${status}${dep.message ? chalk.gray(messageSuffix) : ''}`
105
+ )
106
+ }
107
+
108
+ log(chalk.gray('─'.repeat(100)))
109
+ const syncStatus =
110
+ source === 'api'
111
+ ? chalk.green(' ✓ synced')
112
+ : chalk.yellow(' ⚠ local only')
113
+ log(chalk.gray(`Total: ${deployments.length} deployment(s)`) + syncStatus)
114
+ log('')
115
+ } catch (err) {
116
+ errorWithSuggestions(
117
+ `Failed to list deployments: ${err.message}`,
118
+ [
119
+ 'Check your internet connection',
120
+ 'Use --local flag to show local deployments only',
121
+ 'Try running with --verbose for more details'
122
+ ],
123
+ { verbose, cause: err }
124
+ )
125
+ process.exit(1)
126
+ }
129
127
  }
130
128
 
131
129
  /**
132
130
  * Pad string to the right
133
131
  */
134
- function padRight(str, len) {
135
- if (str.length >= len) return str.substring(0, len - 1) + ' ';
136
- return str + ' '.repeat(len - str.length);
132
+ function padRight (str, len) {
133
+ if (str.length >= len) return `${str.substring(0, len - 1)} `
134
+ return `${str}${' '.repeat(len - str.length)}`
137
135
  }