launchpd 1.0.5 → 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/README.md +23 -22
- package/bin/cli.js +6 -4
- package/package.json +2 -2
- package/src/commands/auth.js +36 -15
- package/src/commands/deploy.js +482 -271
- package/src/utils/api.js +28 -1
- package/src/utils/credentials.js +8 -1
- package/src/utils/errors.js +7 -0
- package/src/utils/quota.js +21 -17
- package/src/utils/remoteSource.js +680 -0
- package/src/utils/upload.js +44 -15
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://github.com/kents00/launchpd)
|
|
8
8
|
[](https://sonarcloud.io/summary/new_code?id=kents00_Launchpd)
|
|
9
|
-
[](https://app.deepsource.com/gh/kents00/Launchpd/)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -46,14 +46,28 @@ _Requires **Node.js 20** or higher._
|
|
|
46
46
|
|
|
47
47
|
### Deployment
|
|
48
48
|
|
|
49
|
-
| Command
|
|
50
|
-
|
|
|
51
|
-
| `launchpd init`
|
|
52
|
-
| `launchpd deploy <folder>`
|
|
53
|
-
| `launchpd deploy .
|
|
54
|
-
| `launchpd deploy . --
|
|
55
|
-
| `launchpd deploy . --
|
|
56
|
-
|
|
49
|
+
| Command | Description |
|
|
50
|
+
| :------------------------------------ | :----------------------------------------------------------------- |
|
|
51
|
+
| `launchpd init` | Link current folder to a subdomain (persisted in `.launchpd.json`) |
|
|
52
|
+
| `launchpd deploy <folder\|url>` | Deploy a local folder or remote URL (GitHub/Gist) |
|
|
53
|
+
| `launchpd deploy . --name <site>` | Deploy with a custom subdomain explicitly |
|
|
54
|
+
| `launchpd deploy . --expires <time>` | Set auto-deletion (e.g., `30m`, `1d`, `7d`) |
|
|
55
|
+
| `launchpd deploy . --open` | Deploy and immediately open the site in your browser |
|
|
56
|
+
|
|
57
|
+
### Remote Deployments (GitHub & Gist)
|
|
58
|
+
|
|
59
|
+
Deploy directly from a public GitHub repository or Gist without cloning it locally:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Deploy from a GitHub repo
|
|
63
|
+
launchpd deploy https://github.com/user/repo
|
|
64
|
+
|
|
65
|
+
# Deploy from a specific branch and subdirectory
|
|
66
|
+
launchpd deploy https://github.com/user/repo --branch main --dir dist
|
|
67
|
+
|
|
68
|
+
# Deploy from a GitHub Gist
|
|
69
|
+
launchpd deploy https://gist.github.com/user/@username
|
|
70
|
+
```
|
|
57
71
|
|
|
58
72
|
### Management
|
|
59
73
|
|
|
@@ -106,16 +120,3 @@ Run `launchpd register` to unlock these benefits!
|
|
|
106
120
|
## License
|
|
107
121
|
|
|
108
122
|
[MIT](LICENSE) © [Kent Edoloverio](https://github.com/kents00)
|
|
109
|
-
|
|
110
|
-
---
|
|
111
|
-
|
|
112
|
-
## Publishing (Maintainers)
|
|
113
|
-
|
|
114
|
-
Publishing is automated from GitHub Releases. Create a release tag like `v1.0.4` and the workflow will:
|
|
115
|
-
|
|
116
|
-
1. Extract the version from the tag
|
|
117
|
-
2. Update `package.json`
|
|
118
|
-
3. Run tests
|
|
119
|
-
4. Publish to npm
|
|
120
|
-
|
|
121
|
-
Ensure `NPM_TOKEN` is set in GitHub Actions secrets.
|
package/bin/cli.js
CHANGED
|
@@ -32,14 +32,16 @@ program
|
|
|
32
32
|
|
|
33
33
|
program
|
|
34
34
|
.command('deploy')
|
|
35
|
-
.description('Deploy a folder to a live URL')
|
|
36
|
-
.argument('[
|
|
35
|
+
.description('Deploy a folder or remote URL to a live URL')
|
|
36
|
+
.argument('[source]', 'Path to folder, GitHub repo URL, or Gist URL', '.')
|
|
37
37
|
.option('--name <subdomain>', 'Use a custom subdomain (optional)')
|
|
38
38
|
.option('-m, --message <text>', 'Deployment message (optional)')
|
|
39
39
|
.option(
|
|
40
40
|
'--expires <time>',
|
|
41
41
|
'Auto-delete after time (e.g., 30m, 2h, 1d). Minimum: 30m'
|
|
42
42
|
)
|
|
43
|
+
.option('--branch <branch>', 'Git branch to deploy from (for repo URLs)')
|
|
44
|
+
.option('--dir <path>', 'Subdirectory within the repo to deploy')
|
|
43
45
|
.option('-y, --yes', 'Auto-confirm all prompts')
|
|
44
46
|
.option('--force', 'Force deployment even with warnings')
|
|
45
47
|
.option(
|
|
@@ -48,8 +50,8 @@ program
|
|
|
48
50
|
)
|
|
49
51
|
.option('--verbose', 'Show detailed error information')
|
|
50
52
|
.option('--qr', 'Show QR code for deployment')
|
|
51
|
-
.action(async (
|
|
52
|
-
await deploy(
|
|
53
|
+
.action(async (source, options) => {
|
|
54
|
+
await deploy(source || '.', options)
|
|
53
55
|
})
|
|
54
56
|
|
|
55
57
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "launchpd",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Deploy static sites instantly to a live URL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"static",
|
|
@@ -51,11 +51,11 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"chalk": "^5.4.0",
|
|
53
53
|
"commander": "^14.0.0",
|
|
54
|
-
"launchpd": "^1.0.0",
|
|
55
54
|
"mime-types": "^2.1.35",
|
|
56
55
|
"nanoid": "^5.1.0",
|
|
57
56
|
"ora": "^8.0.1",
|
|
58
57
|
"qrcode": "^1.5.4",
|
|
58
|
+
"tar": "^7.5.10",
|
|
59
59
|
"update-notifier": "^7.3.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
package/src/commands/auth.js
CHANGED
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
} from '../utils/logger.js'
|
|
24
24
|
import { formatBytes } from '../utils/quota.js'
|
|
25
25
|
import { handleCommonError } from '../utils/errors.js'
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
resendVerification,
|
|
28
|
+
createFetchTimeout,
|
|
29
|
+
API_TIMEOUT_MS
|
|
30
|
+
} from '../utils/api.js'
|
|
27
31
|
import chalk from 'chalk'
|
|
28
32
|
|
|
29
33
|
const API_BASE_URL = config.apiUrl
|
|
@@ -33,7 +37,7 @@ const REGISTER_URL = `https://${config.domain}/`
|
|
|
33
37
|
* Validate API key format
|
|
34
38
|
* Returns true if the key matches expected format: lpd_ followed by alphanumeric/special chars
|
|
35
39
|
*/
|
|
36
|
-
function isValidApiKeyFormat(apiKey) {
|
|
40
|
+
function isValidApiKeyFormat (apiKey) {
|
|
37
41
|
if (!apiKey || typeof apiKey !== 'string') {
|
|
38
42
|
return false
|
|
39
43
|
}
|
|
@@ -45,18 +49,20 @@ function isValidApiKeyFormat(apiKey) {
|
|
|
45
49
|
/**
|
|
46
50
|
* Validate API key with the server
|
|
47
51
|
*/
|
|
48
|
-
async function validateApiKey(apiKey) {
|
|
52
|
+
async function validateApiKey (apiKey) {
|
|
49
53
|
// Validate API key format before sending to network
|
|
50
54
|
// This ensures we only send properly formatted keys, not arbitrary file data
|
|
51
55
|
if (!isValidApiKeyFormat(apiKey)) {
|
|
52
56
|
return null
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
const { signal, clear } = createFetchTimeout(API_TIMEOUT_MS)
|
|
55
60
|
try {
|
|
56
61
|
const response = await fetch(`${API_BASE_URL}/api/quota`, {
|
|
57
62
|
headers: {
|
|
58
63
|
'X-API-Key': apiKey
|
|
59
|
-
}
|
|
64
|
+
},
|
|
65
|
+
signal
|
|
60
66
|
})
|
|
61
67
|
|
|
62
68
|
if (response.status === 401) {
|
|
@@ -76,15 +82,20 @@ async function validateApiKey(apiKey) {
|
|
|
76
82
|
return data
|
|
77
83
|
}
|
|
78
84
|
return null
|
|
79
|
-
} catch {
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (err.name === 'AbortError') {
|
|
87
|
+
return { timeout: true }
|
|
88
|
+
}
|
|
80
89
|
return null
|
|
90
|
+
} finally {
|
|
91
|
+
clear()
|
|
81
92
|
}
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
96
|
* Background update credentials if new data (like apiSecret) is available
|
|
86
97
|
*/
|
|
87
|
-
async function updateCredentialsIfNeeded(creds, result) {
|
|
98
|
+
async function updateCredentialsIfNeeded (creds, result) {
|
|
88
99
|
if (result.user?.api_secret && !creds.apiSecret) {
|
|
89
100
|
await saveCredentials({
|
|
90
101
|
...creds,
|
|
@@ -98,7 +109,7 @@ async function updateCredentialsIfNeeded(creds, result) {
|
|
|
98
109
|
/**
|
|
99
110
|
* Login with API key (original method)
|
|
100
111
|
*/
|
|
101
|
-
async function loginWithApiKey() {
|
|
112
|
+
async function loginWithApiKey () {
|
|
102
113
|
log('Enter your API key from the dashboard.')
|
|
103
114
|
log(`Don't have one? Run ${chalk.cyan('"launchpd register"')} first.\n`)
|
|
104
115
|
|
|
@@ -127,6 +138,16 @@ async function loginWithApiKey() {
|
|
|
127
138
|
return null
|
|
128
139
|
}
|
|
129
140
|
|
|
141
|
+
if (result.timeout) {
|
|
142
|
+
validateSpinner.fail('Request timed out')
|
|
143
|
+
errorWithSuggestions('The server did not respond in time.', [
|
|
144
|
+
'Check your internet connection',
|
|
145
|
+
'Try again later',
|
|
146
|
+
'If the problem persists, check https://status.launchpd.cloud'
|
|
147
|
+
])
|
|
148
|
+
return null
|
|
149
|
+
}
|
|
150
|
+
|
|
130
151
|
if (result.requires_2fa) {
|
|
131
152
|
validateSpinner.fail('2FA Required')
|
|
132
153
|
info(
|
|
@@ -146,7 +167,7 @@ async function loginWithApiKey() {
|
|
|
146
167
|
/**
|
|
147
168
|
* Login command - prompts for API key and validates it
|
|
148
169
|
*/
|
|
149
|
-
export async function login() {
|
|
170
|
+
export async function login () {
|
|
150
171
|
// Check if already logged in
|
|
151
172
|
if (await isLoggedIn()) {
|
|
152
173
|
const creds = await getCredentials()
|
|
@@ -203,7 +224,7 @@ export async function login() {
|
|
|
203
224
|
/**
|
|
204
225
|
* Logout command - clears stored credentials and invalidates server session
|
|
205
226
|
*/
|
|
206
|
-
export async function logout() {
|
|
227
|
+
export async function logout () {
|
|
207
228
|
const loggedIn = await isLoggedIn()
|
|
208
229
|
|
|
209
230
|
if (!loggedIn) {
|
|
@@ -227,7 +248,7 @@ export async function logout() {
|
|
|
227
248
|
/**
|
|
228
249
|
* Register command - opens browser to registration page
|
|
229
250
|
*/
|
|
230
|
-
export function register() {
|
|
251
|
+
export function register () {
|
|
231
252
|
log('\nRegister for Launchpd\n')
|
|
232
253
|
log(`Opening registration page: ${chalk.cyan(REGISTER_URL)}\n`)
|
|
233
254
|
|
|
@@ -273,7 +294,7 @@ export function register() {
|
|
|
273
294
|
/**
|
|
274
295
|
* Whoami command - shows current user info and quota status
|
|
275
296
|
*/
|
|
276
|
-
export async function whoami() {
|
|
297
|
+
export async function whoami () {
|
|
277
298
|
const creds = await getCredentials()
|
|
278
299
|
|
|
279
300
|
if (!creds) {
|
|
@@ -387,7 +408,7 @@ export async function whoami() {
|
|
|
387
408
|
/**
|
|
388
409
|
* Quota command - shows detailed quota information
|
|
389
410
|
*/
|
|
390
|
-
export async function quota() {
|
|
411
|
+
export async function quota () {
|
|
391
412
|
const creds = await getCredentials()
|
|
392
413
|
|
|
393
414
|
if (!creds) {
|
|
@@ -491,7 +512,7 @@ export async function quota() {
|
|
|
491
512
|
/**
|
|
492
513
|
* Create a simple progress bar with color coding
|
|
493
514
|
*/
|
|
494
|
-
function createProgressBar(current, max, width = 20) {
|
|
515
|
+
function createProgressBar (current, max, width = 20) {
|
|
495
516
|
const filled = Math.round((current / max) * width)
|
|
496
517
|
const empty = width - filled
|
|
497
518
|
const percent = (current / max) * 100
|
|
@@ -514,7 +535,7 @@ function createProgressBar(current, max, width = 20) {
|
|
|
514
535
|
/**
|
|
515
536
|
* Get colored percentage text
|
|
516
537
|
*/
|
|
517
|
-
function getPercentColor(percent) {
|
|
538
|
+
function getPercentColor (percent) {
|
|
518
539
|
if (percent >= 90) {
|
|
519
540
|
return chalk.red(`${percent}%`)
|
|
520
541
|
} else if (percent >= 70) {
|
|
@@ -526,7 +547,7 @@ function getPercentColor(percent) {
|
|
|
526
547
|
/**
|
|
527
548
|
* Resend email verification command
|
|
528
549
|
*/
|
|
529
|
-
export async function resendEmailVerification() {
|
|
550
|
+
export async function resendEmailVerification () {
|
|
530
551
|
const loggedIn = await isLoggedIn()
|
|
531
552
|
|
|
532
553
|
if (!loggedIn) {
|