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/rollback.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
getVersionsForSubdomain,
|
|
3
|
+
setActiveVersion,
|
|
4
|
+
getActiveVersion
|
|
5
|
+
} from '../utils/metadata.js'
|
|
6
|
+
import {
|
|
7
|
+
getVersions as getVersionsFromAPI,
|
|
8
|
+
rollbackVersion as rollbackViaAPI
|
|
9
|
+
} from '../utils/api.js'
|
|
10
|
+
import {
|
|
11
|
+
error,
|
|
12
|
+
errorWithSuggestions,
|
|
13
|
+
info,
|
|
14
|
+
warning,
|
|
15
|
+
spinner,
|
|
16
|
+
log
|
|
17
|
+
} from '../utils/logger.js'
|
|
18
|
+
import chalk from 'chalk'
|
|
5
19
|
|
|
6
20
|
/**
|
|
7
21
|
* Rollback a subdomain to a previous version
|
|
@@ -10,115 +24,138 @@ import chalk from 'chalk';
|
|
|
10
24
|
* @param {number} options.to - Specific version to rollback to (optional)
|
|
11
25
|
* @param {boolean} options.verbose - Show verbose error details
|
|
12
26
|
*/
|
|
13
|
-
export async function rollback(subdomainInput, options) {
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
export async function rollback (subdomainInput, options) {
|
|
28
|
+
const subdomain = subdomainInput.toLowerCase()
|
|
29
|
+
const verbose = options.verbose || false
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
try {
|
|
32
|
+
const fetchSpinner = spinner(`Checking versions for ${subdomain}...`)
|
|
19
33
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
// Get all versions for this subdomain (try API first)
|
|
35
|
+
let versions = []
|
|
36
|
+
let currentActive = 1
|
|
37
|
+
let useAPI = false
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (versions.length === 0) {
|
|
42
|
-
fetchSpinner.fail('No deployments found');
|
|
43
|
-
errorWithSuggestions(`No deployments found for subdomain: ${subdomain}`, [
|
|
44
|
-
'Check the subdomain name is correct',
|
|
45
|
-
'Run "launchpd list" to see your deployments',
|
|
46
|
-
], { verbose });
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
39
|
+
const apiResult = await getVersionsFromAPI(subdomain)
|
|
40
|
+
if (apiResult?.versions) {
|
|
41
|
+
versions = apiResult.versions.map((v) => ({
|
|
42
|
+
version: v.version,
|
|
43
|
+
timestamp: v.created_at,
|
|
44
|
+
fileCount: v.file_count,
|
|
45
|
+
message: v.message
|
|
46
|
+
}))
|
|
47
|
+
currentActive = apiResult.activeVersion || 1
|
|
48
|
+
useAPI = true
|
|
49
|
+
} else {
|
|
50
|
+
// Fallback to R2 metadata
|
|
51
|
+
versions = await getVersionsForSubdomain(subdomain)
|
|
52
|
+
currentActive = await getActiveVersion(subdomain)
|
|
53
|
+
}
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
if (versions.length === 0) {
|
|
56
|
+
fetchSpinner.fail('No deployments found')
|
|
57
|
+
errorWithSuggestions(
|
|
58
|
+
`No deployments found for subdomain: ${subdomain}`,
|
|
59
|
+
[
|
|
60
|
+
'Check the subdomain name is correct',
|
|
61
|
+
'Run "launchpd list" to see your deployments'
|
|
62
|
+
],
|
|
63
|
+
{ verbose }
|
|
64
|
+
)
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
if (versions.length === 1) {
|
|
69
|
+
fetchSpinner.warn('Only one version exists')
|
|
70
|
+
warning('Nothing to rollback to.')
|
|
71
|
+
process.exit(1)
|
|
72
|
+
}
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
targetVersion = Number.parseInt(options.to, 10);
|
|
63
|
-
const versionExists = versions.some(v => v.version === targetVersion);
|
|
64
|
-
if (!versionExists) {
|
|
65
|
-
error(`Version ${targetVersion} does not exist.`);
|
|
66
|
-
log('');
|
|
67
|
-
info('Available versions:');
|
|
68
|
-
versions.forEach(v => {
|
|
69
|
-
const isActive = v.version === currentActive;
|
|
70
|
-
const marker = isActive ? chalk.green(' (active)') : '';
|
|
71
|
-
const message = v.message ? ` - "${v.message}"` : '';
|
|
72
|
-
log(` ${chalk.cyan(`v${v.version}`)}${message} - ${chalk.gray(v.timestamp)}${marker}`);
|
|
73
|
-
});
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
// Default: rollback to previous version
|
|
78
|
-
const sortedVersions = versions.map(v => v.version).sort((a, b) => b - a);
|
|
79
|
-
const currentIndex = sortedVersions.indexOf(currentActive);
|
|
80
|
-
if (currentIndex === sortedVersions.length - 1) {
|
|
81
|
-
warning('Already at the oldest version. Cannot rollback further.');
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
targetVersion = sortedVersions[currentIndex + 1];
|
|
85
|
-
}
|
|
74
|
+
fetchSpinner.succeed(`Found ${versions.length} versions`)
|
|
75
|
+
const currentActiveLabel = `v${currentActive}`
|
|
76
|
+
info(`Current active version: ${chalk.cyan(currentActiveLabel)}`)
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
// Determine target version
|
|
79
|
+
let targetVersion = null
|
|
80
|
+
if (options.to) {
|
|
81
|
+
targetVersion = Number.parseInt(options.to, 10)
|
|
82
|
+
const versionExists = versions.some((v) => v.version === targetVersion)
|
|
83
|
+
if (!versionExists) {
|
|
84
|
+
error(`Version ${targetVersion} does not exist.`)
|
|
85
|
+
log('')
|
|
86
|
+
info('Available versions:')
|
|
87
|
+
versions.forEach((v) => {
|
|
88
|
+
const isActive = v.version === currentActive
|
|
89
|
+
const marker = isActive ? chalk.green(' (active)') : ''
|
|
90
|
+
const versionLabel = `v${v.version}`
|
|
91
|
+
const message = v.message ? ` - "${v.message}"` : ''
|
|
92
|
+
log(
|
|
93
|
+
` ${chalk.cyan(versionLabel)}${message} - ${chalk.gray(v.timestamp)}${marker}`
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
process.exit(1)
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// Default: rollback to previous version
|
|
100
|
+
const sortedVersions = versions
|
|
101
|
+
.map((v) => v.version)
|
|
102
|
+
.sort((a, b) => b - a)
|
|
103
|
+
const currentIndex = sortedVersions.indexOf(currentActive)
|
|
104
|
+
if (currentIndex === sortedVersions.length - 1) {
|
|
105
|
+
warning('Already at the oldest version. Cannot rollback further.')
|
|
106
|
+
process.exit(1)
|
|
107
|
+
}
|
|
108
|
+
targetVersion = sortedVersions[currentIndex + 1]
|
|
109
|
+
}
|
|
91
110
|
|
|
92
|
-
|
|
111
|
+
if (targetVersion === currentActive) {
|
|
112
|
+
const targetVersionLabel = `v${targetVersion}`
|
|
113
|
+
warning(`Version ${chalk.cyan(targetVersionLabel)} is already active.`)
|
|
114
|
+
process.exit(0)
|
|
115
|
+
}
|
|
93
116
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const result = await rollbackViaAPI(subdomain, targetVersion);
|
|
98
|
-
if (!result) {
|
|
99
|
-
rollbackSpinner.warn('API unavailable, using local rollback');
|
|
100
|
-
await setActiveVersion(subdomain, targetVersion);
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
await setActiveVersion(subdomain, targetVersion);
|
|
104
|
-
}
|
|
117
|
+
const rollbackSpinner = spinner(
|
|
118
|
+
`Rolling back from v${currentActive} to v${targetVersion}...`
|
|
119
|
+
)
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
// Set the target version as active
|
|
122
|
+
if (useAPI) {
|
|
123
|
+
// Use API for centralized rollback (updates both D1 and R2)
|
|
124
|
+
const result = await rollbackViaAPI(subdomain, targetVersion)
|
|
125
|
+
if (!result) {
|
|
126
|
+
rollbackSpinner.warn('API unavailable, using local rollback')
|
|
127
|
+
await setActiveVersion(subdomain, targetVersion)
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
await setActiveVersion(subdomain, targetVersion)
|
|
131
|
+
}
|
|
108
132
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (targetDeployment?.message) {
|
|
112
|
-
info(`Version message: "${chalk.italic(targetDeployment.message)}"`);
|
|
113
|
-
}
|
|
114
|
-
info(`Restored deployment from: ${chalk.gray(targetDeployment?.timestamp || 'unknown')}`);
|
|
133
|
+
// Find the target version's deployment record for file count
|
|
134
|
+
const targetDeployment = versions.find((v) => v.version === targetVersion)
|
|
115
135
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
rollbackSpinner.succeed(
|
|
137
|
+
(() => {
|
|
138
|
+
const targetVersionLabel = `v${targetVersion}`
|
|
139
|
+
return `Rolled back to ${chalk.cyan(targetVersionLabel)}`
|
|
140
|
+
})()
|
|
141
|
+
)
|
|
142
|
+
log(`\n 🔄 https://${subdomain}.launchpd.cloud\n`)
|
|
143
|
+
if (targetDeployment?.message) {
|
|
144
|
+
info(`Version message: "${chalk.italic(targetDeployment.message)}"`)
|
|
123
145
|
}
|
|
146
|
+
info(
|
|
147
|
+
`Restored deployment from: ${chalk.gray(targetDeployment?.timestamp || 'unknown')}`
|
|
148
|
+
)
|
|
149
|
+
} catch (err) {
|
|
150
|
+
errorWithSuggestions(
|
|
151
|
+
`Rollback failed: ${err.message}`,
|
|
152
|
+
[
|
|
153
|
+
'Check your internet connection',
|
|
154
|
+
'Verify the subdomain and version exist',
|
|
155
|
+
'Run "launchpd versions <subdomain>" to see available versions'
|
|
156
|
+
],
|
|
157
|
+
{ verbose, cause: err }
|
|
158
|
+
)
|
|
159
|
+
process.exit(1)
|
|
160
|
+
}
|
|
124
161
|
}
|
package/src/commands/status.js
CHANGED
|
@@ -1,64 +1,88 @@
|
|
|
1
|
-
import { getProjectConfig, findProjectRoot } from '../utils/projectConfig.js'
|
|
2
|
-
import { getDeployment } from '../utils/api.js'
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { getProjectConfig, findProjectRoot } from '../utils/projectConfig.js'
|
|
2
|
+
import { getDeployment } from '../utils/api.js'
|
|
3
|
+
import {
|
|
4
|
+
errorWithSuggestions,
|
|
5
|
+
info,
|
|
6
|
+
spinner,
|
|
7
|
+
warning,
|
|
8
|
+
formatSize,
|
|
9
|
+
log
|
|
10
|
+
} from '../utils/logger.js'
|
|
11
|
+
import { formatTimeRemaining } from '../utils/expiration.js'
|
|
12
|
+
import chalk from 'chalk'
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* Show current project status
|
|
9
16
|
*/
|
|
10
|
-
export async function status(_options) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
export async function status (_options) {
|
|
18
|
+
const projectRoot = findProjectRoot()
|
|
19
|
+
if (!projectRoot) {
|
|
20
|
+
warning('Not a Launchpd project (no .launchpd.json found)')
|
|
21
|
+
info('Run "launchpd init" to link this directory to a subdomain.')
|
|
22
|
+
return
|
|
23
|
+
}
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
const config = await getProjectConfig(projectRoot)
|
|
26
|
+
if (!config || !config.subdomain) {
|
|
27
|
+
errorWithSuggestions('Invalid project configuration.', [
|
|
28
|
+
'Try deleting .launchpd.json and running "launchpd init" again'
|
|
29
|
+
])
|
|
30
|
+
return
|
|
31
|
+
}
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
info(`Project root: ${chalk.cyan(projectRoot)}`)
|
|
34
|
+
info(
|
|
35
|
+
`Linked subdomain: ${chalk.bold.green(config.subdomain)}.launchpd.cloud`
|
|
36
|
+
)
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
const statusSpinner = spinner('Fetching latest deployment info...')
|
|
39
|
+
try {
|
|
40
|
+
const deploymentData = await getDeployment(config.subdomain)
|
|
41
|
+
statusSpinner.stop()
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
if (deploymentData?.versions && deploymentData.versions.length > 0) {
|
|
44
|
+
const active =
|
|
45
|
+
deploymentData.versions.find(
|
|
46
|
+
(v) => v.version === deploymentData.activeVersion
|
|
47
|
+
) || deploymentData.versions[0]
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
log('\nDeployment Status:')
|
|
50
|
+
const activeVersionLabel = `v${active.version}`
|
|
51
|
+
log(` Active Version: ${chalk.cyan(activeVersionLabel)}`)
|
|
52
|
+
log(
|
|
53
|
+
` Deployed At: ${new Date(active.created_at || active.timestamp).toLocaleString()}`
|
|
54
|
+
)
|
|
55
|
+
if (active.message) {
|
|
56
|
+
log(` Message: ${chalk.italic(active.message)}`)
|
|
57
|
+
}
|
|
58
|
+
log(` File Count: ${active.file_count || active.fileCount}`)
|
|
59
|
+
log(
|
|
60
|
+
` Total Size: ${formatSize(active.total_bytes || active.totalBytes)}`
|
|
61
|
+
)
|
|
45
62
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
// Show expiration if set
|
|
64
|
+
if (active.expires_at || active.expiresAt) {
|
|
65
|
+
const expiryStr = formatTimeRemaining(
|
|
66
|
+
active.expires_at || active.expiresAt
|
|
67
|
+
)
|
|
68
|
+
const expiryColor = expiryStr === 'expired' ? chalk.red : chalk.yellow
|
|
69
|
+
log(` Expires: ${expiryColor(expiryStr)}`)
|
|
70
|
+
}
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
log(
|
|
73
|
+
(() => {
|
|
74
|
+
const url = `https://${config.subdomain}.launchpd.cloud`
|
|
75
|
+
return ` URL: ${chalk.underline.blue(url)}`
|
|
76
|
+
})()
|
|
77
|
+
)
|
|
78
|
+
log('')
|
|
79
|
+
} else {
|
|
80
|
+
warning('\nNo deployments found for this project yet.')
|
|
81
|
+
info('Run "launchpd deploy <folder>" to push your first version.')
|
|
63
82
|
}
|
|
83
|
+
} catch {
|
|
84
|
+
statusSpinner.fail('Failed to fetch deployment status')
|
|
85
|
+
info(`Subdomain: ${config.subdomain}`)
|
|
86
|
+
// Don't exit, just show what we have
|
|
87
|
+
}
|
|
64
88
|
}
|