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.
@@ -1,7 +1,21 @@
1
- import { getVersionsForSubdomain, setActiveVersion, getActiveVersion } from '../utils/metadata.js';
2
- import { getVersions as getVersionsFromAPI, rollbackVersion as rollbackViaAPI } from '../utils/api.js';
3
- import { error, errorWithSuggestions, info, warning, spinner, log } from '../utils/logger.js';
4
- import chalk from 'chalk';
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
- const subdomain = subdomainInput.toLowerCase();
15
- const verbose = options.verbose || false;
27
+ export async function rollback (subdomainInput, options) {
28
+ const subdomain = subdomainInput.toLowerCase()
29
+ const verbose = options.verbose || false
16
30
 
17
- try {
18
- const fetchSpinner = spinner(`Checking versions for ${subdomain}...`);
31
+ try {
32
+ const fetchSpinner = spinner(`Checking versions for ${subdomain}...`)
19
33
 
20
- // Get all versions for this subdomain (try API first)
21
- let versions = [];
22
- let currentActive = 1;
23
- let useAPI = false;
34
+ // Get all versions for this subdomain (try API first)
35
+ let versions = []
36
+ let currentActive = 1
37
+ let useAPI = false
24
38
 
25
- const apiResult = await getVersionsFromAPI(subdomain);
26
- if (apiResult && apiResult.versions) {
27
- versions = apiResult.versions.map(v => ({
28
- version: v.version,
29
- timestamp: v.created_at,
30
- fileCount: v.file_count,
31
- message: v.message,
32
- }));
33
- currentActive = apiResult.activeVersion || 1;
34
- useAPI = true;
35
- } else {
36
- // Fallback to R2 metadata
37
- versions = await getVersionsForSubdomain(subdomain);
38
- currentActive = await getActiveVersion(subdomain);
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
- if (versions.length === 1) {
51
- fetchSpinner.warn('Only one version exists');
52
- warning('Nothing to rollback to.');
53
- process.exit(1);
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
- fetchSpinner.succeed(`Found ${versions.length} versions`);
57
- info(`Current active version: ${chalk.cyan(`v${currentActive}`)}`);
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
- // Determine target version
60
- let targetVersion;
61
- if (options.to) {
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
- if (targetVersion === currentActive) {
88
- warning(`Version ${chalk.cyan(`v${targetVersion}`)} is already active.`);
89
- process.exit(0);
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
- const rollbackSpinner = spinner(`Rolling back from v${currentActive} to v${targetVersion}...`);
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
- // Set the target version as active
95
- if (useAPI) {
96
- // Use API for centralized rollback (updates both D1 and R2)
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
- // Find the target version's deployment record for file count
107
- const targetDeployment = versions.find(v => v.version === targetVersion);
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
- rollbackSpinner.succeed(`Rolled back to ${chalk.cyan(`v${targetVersion}`)}`);
110
- log(`\n 🔄 https://${subdomain}.launchpd.cloud\n`);
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
- } catch (err) {
117
- errorWithSuggestions(`Rollback failed: ${err.message}`, [
118
- 'Check your internet connection',
119
- 'Verify the subdomain and version exist',
120
- 'Run "launchpd versions <subdomain>" to see available versions',
121
- ], { verbose, cause: err });
122
- process.exit(1);
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
  }
@@ -1,64 +1,88 @@
1
- import { getProjectConfig, findProjectRoot } from '../utils/projectConfig.js';
2
- import { getDeployment } from '../utils/api.js';
3
- import { errorWithSuggestions, info, spinner, warning, formatSize, log } from '../utils/logger.js';
4
- import { formatTimeRemaining } from '../utils/expiration.js';
5
- import chalk from 'chalk';
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
- const projectRoot = findProjectRoot();
12
- if (!projectRoot) {
13
- warning('Not a Launchpd project (no .launchpd.json found)');
14
- info('Run "launchpd init" to link this directory to a subdomain.');
15
- return;
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
- const config = await getProjectConfig(projectRoot);
19
- if (!config || !config.subdomain) {
20
- errorWithSuggestions('Invalid project configuration.', [
21
- 'Try deleting .launchpd.json and running "launchpd init" again'
22
- ]);
23
- return;
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
- info(`Project root: ${chalk.cyan(projectRoot)}`);
27
- info(`Linked subdomain: ${chalk.bold.green(config.subdomain)}.launchpd.cloud`);
33
+ info(`Project root: ${chalk.cyan(projectRoot)}`)
34
+ info(
35
+ `Linked subdomain: ${chalk.bold.green(config.subdomain)}.launchpd.cloud`
36
+ )
28
37
 
29
- const statusSpinner = spinner('Fetching latest deployment info...');
30
- try {
31
- const deploymentData = await getDeployment(config.subdomain);
32
- statusSpinner.stop();
38
+ const statusSpinner = spinner('Fetching latest deployment info...')
39
+ try {
40
+ const deploymentData = await getDeployment(config.subdomain)
41
+ statusSpinner.stop()
33
42
 
34
- if (deploymentData && deploymentData.versions && deploymentData.versions.length > 0) {
35
- const active = deploymentData.versions.find(v => v.version === deploymentData.activeVersion) || deploymentData.versions[0];
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
- log('\nDeployment Status:');
38
- log(` Active Version: ${chalk.cyan(`v${active.version}`)}`);
39
- log(` Deployed At: ${new Date(active.created_at || active.timestamp).toLocaleString()}`);
40
- if (active.message) {
41
- log(` Message: ${chalk.italic(active.message)}`);
42
- }
43
- log(` File Count: ${active.file_count || active.fileCount}`);
44
- log(` Total Size: ${formatSize(active.total_bytes || active.totalBytes)}`);
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
- // Show expiration if set
47
- if (active.expires_at || active.expiresAt) {
48
- const expiryStr = formatTimeRemaining(active.expires_at || active.expiresAt);
49
- const expiryColor = expiryStr === 'expired' ? chalk.red : chalk.yellow;
50
- log(` Expires: ${expiryColor(expiryStr)}`);
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
- log(` URL: ${chalk.underline.blue(`https://${config.subdomain}.launchpd.cloud`)}`);
54
- log('');
55
- } else {
56
- warning('\nNo deployments found for this project yet.');
57
- info('Run "launchpd deploy <folder>" to push your first version.');
58
- }
59
- } catch {
60
- statusSpinner.fail('Failed to fetch deployment status');
61
- info(`Subdomain: ${config.subdomain}`);
62
- // Don't exit, just show what we have
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
  }