mudhost 1.0.0
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 +85 -0
- package/bin/hosting.js +9 -0
- package/package.json +42 -0
- package/src/auth.js +98 -0
- package/src/cli.js +183 -0
- package/src/deploy.js +177 -0
- package/src/utils.js +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
## Quick Start
|
|
2
|
+
|
|
3
|
+
#### Login to your Hosting instance:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx mudhost login --api https://your-domain.com
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Alternatively, you can provide username and password directly:
|
|
10
|
+
```
|
|
11
|
+
mudhost login -u admin -p admin123 --api https://your-domain.com
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
#### Deploy your project:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx mudhost deploy my-project ./dist
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
#### Access your preview:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
https://my-project-main-12345.your-domain.com
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
Login
|
|
29
|
+
```bash
|
|
30
|
+
npx mudhost login [options]
|
|
31
|
+
```
|
|
32
|
+
Deploy
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx mudhost deploy <project-id> [dist-dir] [options]
|
|
36
|
+
```
|
|
37
|
+
List Deployments
|
|
38
|
+
```bash
|
|
39
|
+
npx mudhost list [options]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Delete Deployment
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx mudhost delete <deployment-id>
|
|
46
|
+
```
|
|
47
|
+
Whoami
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx mudhost whoami
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Logout
|
|
54
|
+
```bash
|
|
55
|
+
npx mudhost logout
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### Examples
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Deploy with custom branch
|
|
63
|
+
npx mudhost deploy my-app ./build --branch feature/new-ui
|
|
64
|
+
|
|
65
|
+
# Deploy to custom API
|
|
66
|
+
npx mudhost deploy my-app ./dist --api https://staging.example.com
|
|
67
|
+
|
|
68
|
+
# List deployments for a project
|
|
69
|
+
npx mudhost list --project my-app
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
The CLI automatically saves your authentication token and API URL in ~/.mudhost/config.json.
|
|
76
|
+
|
|
77
|
+
You can also create a mudhost.json file in your project:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"apiUrl": "https://your-domain.com",
|
|
82
|
+
"defaultProject": "my-app",
|
|
83
|
+
"defaultBranch": "main"
|
|
84
|
+
}
|
|
85
|
+
```
|
package/bin/hosting.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mudhost",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI for Hosting - deploy preview environments with one command",
|
|
5
|
+
"main": "src/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mudhost": "./bin/hosting.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node src/cli.js",
|
|
11
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"hosting",
|
|
15
|
+
"preview",
|
|
16
|
+
"deployment",
|
|
17
|
+
"cli",
|
|
18
|
+
"mudhost"
|
|
19
|
+
],
|
|
20
|
+
"author": "Irustm",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/irustm/mudhost"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"axios": "^1.6.0",
|
|
28
|
+
"chalk": "^4.1.2",
|
|
29
|
+
"commander": "^11.1.0",
|
|
30
|
+
"inquirer": "^8.2.6",
|
|
31
|
+
"fs-extra": "^11.1.1",
|
|
32
|
+
"glob": "^10.3.0",
|
|
33
|
+
"mime-types": "^2.1.35",
|
|
34
|
+
"ora": "^5.4.1"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=14.0.0"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { ConfigManager } = require('./utils');
|
|
7
|
+
|
|
8
|
+
class AuthManager {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.config = new ConfigManager();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async login(apiUrl, options) {
|
|
14
|
+
let { username, password } = options;
|
|
15
|
+
|
|
16
|
+
// Если credentials не предоставлены, запросим их
|
|
17
|
+
if (!username || !password) {
|
|
18
|
+
const answers = await inquirer.prompt([
|
|
19
|
+
{
|
|
20
|
+
type: 'input',
|
|
21
|
+
name: 'username',
|
|
22
|
+
message: 'Username:',
|
|
23
|
+
when: !username
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'password',
|
|
27
|
+
name: 'password',
|
|
28
|
+
message: 'Password:',
|
|
29
|
+
when: !password
|
|
30
|
+
}
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
username = username || answers.username;
|
|
34
|
+
password = password || answers.password;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!username || !password) {
|
|
38
|
+
throw new Error('Username and password are required');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const response = await axios.post(`${apiUrl}/api/auth/login`, {
|
|
43
|
+
username,
|
|
44
|
+
password
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { token, user } = response.data;
|
|
48
|
+
|
|
49
|
+
// Сохраняем токен и настройки
|
|
50
|
+
await this.config.set('token', token);
|
|
51
|
+
await this.config.set('apiUrl', apiUrl);
|
|
52
|
+
await this.config.set('user', user);
|
|
53
|
+
|
|
54
|
+
console.log(chalk.green(`✅ Logged in as ${user.username} (${user.role})`));
|
|
55
|
+
return token;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error.response?.status === 401) {
|
|
58
|
+
throw new Error('Invalid credentials');
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Login failed: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getAuthHeaders() {
|
|
65
|
+
const token = await this.config.get('token');
|
|
66
|
+
if (!token) {
|
|
67
|
+
throw new Error('Not authenticated. Please run "mudhost login" first.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
'Authorization': `Bearer ${token}`,
|
|
72
|
+
'Content-Type': 'application/json'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async whoami(apiUrl) {
|
|
77
|
+
try {
|
|
78
|
+
const headers = await this.getAuthHeaders();
|
|
79
|
+
const response = await axios.get(`${apiUrl}/api/auth/me`, { headers });
|
|
80
|
+
|
|
81
|
+
const user = response.data.user;
|
|
82
|
+
const config = await this.config.getAll();
|
|
83
|
+
|
|
84
|
+
console.log(chalk.blue('👤 Current User:'));
|
|
85
|
+
console.log(` Username: ${chalk.bold(user.username)}`);
|
|
86
|
+
console.log(` Role: ${chalk.bold(user.role)}`);
|
|
87
|
+
console.log(` API: ${chalk.bold(config.apiUrl)}`);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error(`Failed to get user info: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
logout() {
|
|
94
|
+
this.config.clear();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { AuthManager };
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const { Command } = require('commander');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { AuthManager } = require('./auth');
|
|
4
|
+
const { DeployManager } = require('./deploy');
|
|
5
|
+
const { ConfigManager } = require('./utils');
|
|
6
|
+
|
|
7
|
+
class CLI {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.program = new Command();
|
|
10
|
+
this.auth = new AuthManager();
|
|
11
|
+
this.deploy = new DeployManager();
|
|
12
|
+
this.config = new ConfigManager();
|
|
13
|
+
this.setupCommands();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setupCommands() {
|
|
17
|
+
this.program
|
|
18
|
+
.name('mudhost')
|
|
19
|
+
.description('CLI for Mud Hosting - Deploy preview environments')
|
|
20
|
+
.version('1.0.0');
|
|
21
|
+
|
|
22
|
+
// Login command
|
|
23
|
+
this.program
|
|
24
|
+
.command('login')
|
|
25
|
+
.description('Login to Mud Hosting')
|
|
26
|
+
.option('-u, --username <username>', 'Username')
|
|
27
|
+
.option('-p, --password <password>', 'Password')
|
|
28
|
+
.option('--api <url>', 'API URL', 'http://localhost:8000')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
await this.handleLogin(options);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Deploy command
|
|
34
|
+
this.program
|
|
35
|
+
.command('deploy')
|
|
36
|
+
.description('Deploy a project')
|
|
37
|
+
.argument('<project-id>', 'Project ID')
|
|
38
|
+
.argument('[dist-dir]', 'Distribution directory', './dist')
|
|
39
|
+
.option('-b, --branch <branch>', 'Git branch', 'main')
|
|
40
|
+
.option('-c, --commit <commit>', 'Git commit', 'latest')
|
|
41
|
+
.option('--api <url>', 'API URL')
|
|
42
|
+
.option('--config <file>', 'Config file')
|
|
43
|
+
.action(async (projectId, distDir, options) => {
|
|
44
|
+
await this.handleDeploy(projectId, distDir, options);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// List command
|
|
48
|
+
this.program
|
|
49
|
+
.command('list')
|
|
50
|
+
.description('List deployments')
|
|
51
|
+
.option('--api <url>', 'API URL')
|
|
52
|
+
.option('-p, --project <projectId>', 'Filter by project')
|
|
53
|
+
.action(async (options) => {
|
|
54
|
+
await this.handleList(options);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Delete command
|
|
58
|
+
this.program
|
|
59
|
+
.command('delete')
|
|
60
|
+
.description('Delete a deployment')
|
|
61
|
+
.argument('<deployment-id>', 'Deployment ID')
|
|
62
|
+
.option('--api <url>', 'API URL')
|
|
63
|
+
.action(async (deploymentId, options) => {
|
|
64
|
+
await this.handleDelete(deploymentId, options);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Whoami command
|
|
68
|
+
this.program
|
|
69
|
+
.command('whoami')
|
|
70
|
+
.description('Show current user info')
|
|
71
|
+
.option('--api <url>', 'API URL')
|
|
72
|
+
.action(async (options) => {
|
|
73
|
+
await this.handleWhoami(options);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Logout command
|
|
77
|
+
this.program
|
|
78
|
+
.command('logout')
|
|
79
|
+
.description('Logout and clear saved token')
|
|
80
|
+
.action(() => {
|
|
81
|
+
this.handleLogout();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Init command
|
|
85
|
+
this.program
|
|
86
|
+
.command('init')
|
|
87
|
+
.description('Create config file')
|
|
88
|
+
.action(() => {
|
|
89
|
+
this.handleInit();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async handleLogin(options) {
|
|
94
|
+
const apiUrl = options.api || await this.config.get('apiUrl') || 'http://localhost:8000';
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await this.auth.login(apiUrl, options);
|
|
98
|
+
console.log(chalk.green('✅ Login successful!'));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(chalk.red('❌ Login failed:'), error.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async handleDeploy(projectId, distDir, options) {
|
|
106
|
+
const apiUrl = options.api || await this.config.get('apiUrl');
|
|
107
|
+
|
|
108
|
+
if (!apiUrl) {
|
|
109
|
+
console.error(chalk.red('❌ API URL not configured. Please login first or use --api option.'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
await this.deploy.deploy(projectId, distDir, options, apiUrl);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(chalk.red('❌ Deployment failed:'), error.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async handleList(options) {
|
|
122
|
+
const apiUrl = options.api || await this.config.get('apiUrl');
|
|
123
|
+
|
|
124
|
+
if (!apiUrl) {
|
|
125
|
+
console.error(chalk.red('❌ API URL not configured. Please login first or use --api option.'));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await this.deploy.listDeployments(apiUrl, options.project);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(chalk.red('❌ Failed to list deployments:'), error.message);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async handleDelete(deploymentId, options) {
|
|
138
|
+
const apiUrl = options.api || await this.config.get('apiUrl');
|
|
139
|
+
|
|
140
|
+
if (!apiUrl) {
|
|
141
|
+
console.error(chalk.red('❌ API URL not configured. Please login first or use --api option.'));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
await this.deploy.deleteDeployment(deploymentId, apiUrl);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error(chalk.red('❌ Failed to delete deployment:'), error.message);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async handleWhoami(options) {
|
|
154
|
+
const apiUrl = options.api || await this.config.get('apiUrl');
|
|
155
|
+
|
|
156
|
+
if (!apiUrl) {
|
|
157
|
+
console.error(chalk.red('❌ API URL not configured. Please login first or use --api option.'));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await this.auth.whoami(apiUrl);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(chalk.red('❌ Failed to get user info:'), error.message);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
handleLogout() {
|
|
170
|
+
this.auth.logout();
|
|
171
|
+
console.log(chalk.green('✅ Logged out successfully!'));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
handleInit() {
|
|
175
|
+
this.config.createConfig();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async run() {
|
|
179
|
+
await this.program.parseAsync(process.argv);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = { CLI };
|
package/src/deploy.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const glob = require('glob');
|
|
6
|
+
const mime = require('mime-types');
|
|
7
|
+
const ora = require('ora');
|
|
8
|
+
const { AuthManager } = require('./auth');
|
|
9
|
+
|
|
10
|
+
class DeployManager {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.auth = new AuthManager();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async collectFiles(distDir) {
|
|
16
|
+
const spinner = ora('📁 Scanning files...').start();
|
|
17
|
+
|
|
18
|
+
if (!await fs.pathExists(distDir)) {
|
|
19
|
+
spinner.fail();
|
|
20
|
+
throw new Error(`Directory not found: ${distDir}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const files = {};
|
|
25
|
+
const allFiles = glob.sync('**/*', {
|
|
26
|
+
cwd: distDir,
|
|
27
|
+
nodir: true,
|
|
28
|
+
dot: true
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
for (const filePath of allFiles) {
|
|
32
|
+
const fullPath = path.join(distDir, filePath);
|
|
33
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
34
|
+
files[filePath] = content;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
spinner.succeed(`Found ${allFiles.length} files`);
|
|
38
|
+
return files;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail();
|
|
41
|
+
throw new Error(`Failed to read files: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async deploy(projectId, distDir, options, apiUrl) {
|
|
46
|
+
const spinner = ora('🚀 Deploying...').start();
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Собираем файлы
|
|
50
|
+
const files = await this.collectFiles(distDir);
|
|
51
|
+
|
|
52
|
+
if (Object.keys(files).length === 0) {
|
|
53
|
+
spinner.fail();
|
|
54
|
+
throw new Error(`No files found in ${distDir}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Получаем заголовки аутентификации
|
|
58
|
+
const headers = await this.auth.getAuthHeaders();
|
|
59
|
+
|
|
60
|
+
// Отправляем деплой
|
|
61
|
+
spinner.text = '📦 Uploading deployment...';
|
|
62
|
+
|
|
63
|
+
const response = await axios.post(`${apiUrl}/api/deployments`, {
|
|
64
|
+
projectId,
|
|
65
|
+
branch: options.branch,
|
|
66
|
+
commit: options.commit,
|
|
67
|
+
files
|
|
68
|
+
}, { headers });
|
|
69
|
+
|
|
70
|
+
const deployment = response.data;
|
|
71
|
+
|
|
72
|
+
spinner.succeed('✅ Deployment successful!');
|
|
73
|
+
|
|
74
|
+
console.log(chalk.green('\n📊 Deployment Info:'));
|
|
75
|
+
console.log(` Project: ${chalk.bold(deployment.projectId)}`);
|
|
76
|
+
console.log(` Branch: ${chalk.bold(deployment.branch)}`);
|
|
77
|
+
console.log(` ID: ${chalk.bold(deployment.id)}`);
|
|
78
|
+
console.log(` Files: ${chalk.bold(deployment.files.length)}`);
|
|
79
|
+
|
|
80
|
+
console.log(chalk.blue('\n🌐 Preview URLs:'));
|
|
81
|
+
deployment.urls?.forEach(url => {
|
|
82
|
+
console.log(` ${chalk.cyan('→')} ${chalk.underline(url)}`);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (deployment.url) {
|
|
86
|
+
console.log(` ${chalk.cyan('→')} ${chalk.underline(deployment.url)}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return deployment;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
spinner.fail();
|
|
92
|
+
|
|
93
|
+
if (error.response) {
|
|
94
|
+
const status = error.response.status;
|
|
95
|
+
const message = error.response.data?.error || error.message;
|
|
96
|
+
|
|
97
|
+
switch (status) {
|
|
98
|
+
case 401:
|
|
99
|
+
throw new Error('Authentication failed. Please login again.');
|
|
100
|
+
case 413:
|
|
101
|
+
throw new Error('Deployment too large. Reduce file size.');
|
|
102
|
+
default:
|
|
103
|
+
throw new Error(`HTTP ${status}: ${message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
throw new Error(`Deployment failed: ${error.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async listDeployments(apiUrl, projectFilter) {
|
|
112
|
+
const spinner = ora('📋 Loading deployments...').start();
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const headers = await this.auth.getAuthHeaders();
|
|
116
|
+
const response = await axios.get(`${apiUrl}/api/deployments`, { headers });
|
|
117
|
+
|
|
118
|
+
let deployments = response.data;
|
|
119
|
+
|
|
120
|
+
// Фильтруем по проекту если нужно
|
|
121
|
+
if (projectFilter) {
|
|
122
|
+
deployments = deployments.filter(d => d.projectId === projectFilter);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
spinner.succeed(`Found ${deployments.length} deployments`);
|
|
126
|
+
|
|
127
|
+
if (deployments.length === 0) {
|
|
128
|
+
console.log(chalk.yellow('No deployments found'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log('\n' + chalk.blue.bold('📦 Deployments:'));
|
|
133
|
+
console.log('=' .repeat(80));
|
|
134
|
+
|
|
135
|
+
deployments.forEach(deployment => {
|
|
136
|
+
const status = deployment.isExpired ? chalk.red('❌ EXPIRED') : chalk.green('✅ ACTIVE');
|
|
137
|
+
const daysLeft = deployment.isExpired ?
|
|
138
|
+
'EXPIRED' :
|
|
139
|
+
`${Math.ceil(deployment.expiresIn / (1000 * 60 * 60 * 24))} days`;
|
|
140
|
+
|
|
141
|
+
console.log(`🆔 ${chalk.bold(deployment.id)}`);
|
|
142
|
+
console.log(` 📁 ${chalk.gray('Project:')} ${deployment.projectId}`);
|
|
143
|
+
console.log(` 🌿 ${chalk.gray('Branch:')} ${deployment.branch}`);
|
|
144
|
+
console.log(` 🔗 ${chalk.gray('URL:')} ${chalk.cyan(deployment.url)}`);
|
|
145
|
+
console.log(` 📅 ${chalk.gray('Created:')} ${new Date(deployment.createdAt).toLocaleString()}`);
|
|
146
|
+
console.log(` ⏰ ${chalk.gray('Status:')} ${status} (${daysLeft} left)`);
|
|
147
|
+
console.log(` 📊 ${chalk.gray('Files:')} ${deployment.files.length}`);
|
|
148
|
+
console.log('-'.repeat(80));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
} catch (error) {
|
|
152
|
+
spinner.fail();
|
|
153
|
+
throw new Error(`Failed to list deployments: ${error.message}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async deleteDeployment(deploymentId, apiUrl) {
|
|
158
|
+
const spinner = ora('🗑️ Deleting deployment...').start();
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const headers = await this.auth.getAuthHeaders();
|
|
162
|
+
await axios.delete(`${apiUrl}/api/deployments/${deploymentId}`, { headers });
|
|
163
|
+
|
|
164
|
+
spinner.succeed(`✅ Deployment ${deploymentId} deleted successfully`);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
spinner.fail();
|
|
167
|
+
|
|
168
|
+
if (error.response?.status === 404) {
|
|
169
|
+
throw new Error(`Deployment not found: ${deploymentId}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
throw new Error(`Failed to delete deployment: ${error.message}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { DeployManager };
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
|
|
6
|
+
class ConfigManager {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.configDir = path.join(os.homedir(), '.mudhost');
|
|
9
|
+
this.configFile = path.join(this.configDir, 'config.json');
|
|
10
|
+
this.ensureConfigDir();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
ensureConfigDir() {
|
|
14
|
+
if (!fs.existsSync(this.configDir)) {
|
|
15
|
+
fs.mkdirSync(this.configDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async get(key) {
|
|
20
|
+
try {
|
|
21
|
+
const config = await this.getAll();
|
|
22
|
+
return config[key];
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getAll() {
|
|
29
|
+
try {
|
|
30
|
+
if (await fs.pathExists(this.configFile)) {
|
|
31
|
+
return await fs.readJson(this.configFile);
|
|
32
|
+
}
|
|
33
|
+
return {};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async set(key, value) {
|
|
40
|
+
const config = await this.getAll();
|
|
41
|
+
config[key] = value;
|
|
42
|
+
await fs.writeJson(this.configFile, config, { spaces: 2 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async clear() {
|
|
46
|
+
if (await fs.pathExists(this.configFile)) {
|
|
47
|
+
await fs.remove(this.configFile);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
createConfig() {
|
|
52
|
+
const configTemplate = {
|
|
53
|
+
apiUrl: 'http://localhost:8000',
|
|
54
|
+
defaultProject: 'my-app',
|
|
55
|
+
defaultBranch: 'main'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const configPath = path.join(process.cwd(), 'mudhost.json');
|
|
59
|
+
|
|
60
|
+
if (fs.existsSync(configPath)) {
|
|
61
|
+
console.log(chalk.yellow('⚠️ Config file already exists'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fs.writeJsonSync(configPath, configTemplate, { spaces: 2 });
|
|
66
|
+
console.log(chalk.green('✅ Created mudhost.json config file'));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { ConfigManager };
|