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.
- package/LICENSE +21 -21
- package/README.md +61 -39
- package/bin/cli.js +149 -124
- package/bin/setup.js +42 -40
- package/package.json +4 -4
- package/src/commands/auth.js +516 -522
- package/src/commands/deploy.js +745 -386
- package/src/commands/index.js +14 -7
- package/src/commands/init.js +95 -72
- package/src/commands/list.js +120 -122
- package/src/commands/rollback.js +139 -102
- package/src/commands/status.js +75 -51
- package/src/commands/versions.js +153 -113
- package/src/config.js +32 -31
- package/src/utils/api.js +220 -195
- package/src/utils/credentials.js +88 -85
- package/src/utils/endpoint.js +58 -0
- package/src/utils/errors.js +79 -69
- package/src/utils/expiration.js +49 -47
- package/src/utils/id.js +5 -5
- package/src/utils/ignore.js +35 -36
- package/src/utils/index.js +10 -11
- package/src/utils/localConfig.js +39 -43
- package/src/utils/logger.js +113 -106
- package/src/utils/machineId.js +15 -19
- package/src/utils/metadata.js +113 -87
- package/src/utils/projectConfig.js +48 -45
- package/src/utils/prompt.js +91 -82
- package/src/utils/quota.js +261 -225
- package/src/utils/remoteSource.js +680 -0
- package/src/utils/upload.js +197 -127
- package/src/utils/validator.js +116 -68
package/src/commands/index.js
CHANGED
|
@@ -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 {
|
|
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'
|
package/src/commands/init.js
CHANGED
|
@@ -1,90 +1,113 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
52
|
+
let subdomain = options.name
|
|
34
53
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
const checkSpinner = spinner(`Checking if "${subdomain}" is available...`)
|
|
68
|
+
try {
|
|
69
|
+
const isAvailable = await checkSubdomainAvailable(subdomain)
|
|
70
|
+
let owned = false
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
}
|
package/src/commands/list.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { getLocalDeployments } from '../utils/localConfig.js'
|
|
2
|
-
import { listDeployments as listFromAPI } from '../utils/api.js'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
}
|