remote-deploy-cli 1.1.1 → 1.2.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/.prettierignore +9 -0
- package/.prettierrc +7 -0
- package/README.md +11 -1
- package/bin/index.js +17 -3
- package/lib/client/index.js +30 -11
- package/lib/server/server.js +27 -0
- package/package.json +10 -2
- package/scripts/update.js +90 -0
- package/server-entry.js +22 -0
- package/sonar-project.js +18 -0
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/README.md
CHANGED
|
@@ -71,7 +71,9 @@ redep deploy <type>
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
**Parameters:**
|
|
74
|
-
- `<type>`: The service type to deploy.
|
|
74
|
+
- `<type>`: The service type to deploy.
|
|
75
|
+
- `fe`: Frontend (pre-configured to run `docker compose pull && docker compose up -d`).
|
|
76
|
+
- `custom`: Custom command (configured on server via `deployment_command`).
|
|
75
77
|
|
|
76
78
|
**Requirements:**
|
|
77
79
|
- `SERVER_URL` must be configured.
|
|
@@ -81,6 +83,9 @@ redep deploy <type>
|
|
|
81
83
|
```bash
|
|
82
84
|
# Deploy frontend service
|
|
83
85
|
redep deploy fe
|
|
86
|
+
|
|
87
|
+
# Deploy custom command
|
|
88
|
+
redep deploy custom
|
|
84
89
|
```
|
|
85
90
|
|
|
86
91
|
**Expected Output:**
|
|
@@ -155,6 +160,9 @@ redep config set working_dir /path/to/project
|
|
|
155
160
|
|
|
156
161
|
# Set Secret Key
|
|
157
162
|
redep config set secret_key my-secret-key
|
|
163
|
+
|
|
164
|
+
# Set Custom Deployment Command (Optional)
|
|
165
|
+
redep config set deployment_command "git pull && npm install && pm2 restart app"
|
|
158
166
|
```
|
|
159
167
|
|
|
160
168
|
#### Running with Docker (Recommended)
|
|
@@ -172,6 +180,7 @@ services:
|
|
|
172
180
|
environment:
|
|
173
181
|
- WORKING_DIR=/workspace
|
|
174
182
|
- SECRET_KEY=secure-key
|
|
183
|
+
- DEPLOYMENT_COMMAND=docker compose restart app
|
|
175
184
|
```
|
|
176
185
|
|
|
177
186
|
---
|
|
@@ -208,6 +217,7 @@ sequenceDiagram
|
|
|
208
217
|
| :------------ | :------------ | :----------------------------------------------- | :--------------- |
|
|
209
218
|
| `server_port` | `SERVER_PORT` | Port for the server to listen on (Default: 3000) | **Server** |
|
|
210
219
|
| `working_dir` | `WORKING_DIR` | Directory to execute commands in | **Server** |
|
|
220
|
+
| `deployment_command` | `DEPLOYMENT_COMMAND` | Custom command for `deploy custom` | **Server** |
|
|
211
221
|
| `server_url` | `SERVER_URL` | URL of the remote `redep` server | **Client** |
|
|
212
222
|
| `secret_key` | `SECRET_KEY` | Shared secret for authentication | **Both** |
|
|
213
223
|
|
package/bin/index.js
CHANGED
|
@@ -66,14 +66,28 @@ program
|
|
|
66
66
|
// We'll use a dynamic import or checking for the pm2 binary in a real scenario
|
|
67
67
|
// But here we can just try to spawn 'pm2' command
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// Use dedicated server entry point for PM2 to avoid CLI/ESM issues
|
|
70
|
+
// Resolve absolute path to server-entry.js
|
|
71
|
+
const scriptPath = new URL('../server-entry.js', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1');
|
|
72
|
+
|
|
73
|
+
const args = [
|
|
74
|
+
'start',
|
|
75
|
+
scriptPath,
|
|
76
|
+
'--name', 'redep-server'
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// We don't pass 'listen' arg because server-entry.js starts immediately
|
|
80
|
+
// But we do need to ensure env vars are passed if port is customized
|
|
81
|
+
|
|
82
|
+
const env = { ...process.env };
|
|
70
83
|
if (options.port) {
|
|
71
|
-
|
|
84
|
+
env.SERVER_PORT = options.port;
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
const pm2 = spawn('pm2', args, {
|
|
75
88
|
stdio: 'inherit',
|
|
76
|
-
shell: true
|
|
89
|
+
shell: true,
|
|
90
|
+
env: env // Pass modified env with port
|
|
77
91
|
});
|
|
78
92
|
|
|
79
93
|
pm2.on('error', () => {
|
package/lib/client/index.js
CHANGED
|
@@ -16,16 +16,35 @@ export const deploy = async (type, serverUrl, secret) => {
|
|
|
16
16
|
if (result.output.stdout) console.log(result.output.stdout);
|
|
17
17
|
if (result.output.stderr) console.log(result.output.stderr);
|
|
18
18
|
logger.log('---------------------');
|
|
19
|
+
} else if (type === 'custom') {
|
|
20
|
+
logger.info('Sending custom deployment instruction to server machine...');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const result = await sendCommand(serverUrl, '/deploy/custom', secret, {});
|
|
24
|
+
|
|
25
|
+
if (result.status === 'success') {
|
|
26
|
+
logger.success('Server successfully executed the custom deployment command.');
|
|
27
|
+
logger.log('--- Remote Output ---');
|
|
28
|
+
if (result.output.stdout) console.log(result.output.stdout);
|
|
29
|
+
if (result.output.stderr) console.log(result.output.stderr);
|
|
30
|
+
logger.log('---------------------');
|
|
31
|
+
} else {
|
|
32
|
+
logger.error('Server reported failure.');
|
|
33
|
+
console.error(result);
|
|
34
|
+
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
19
38
|
} else {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
logger.error('Server reported failure.');
|
|
40
|
+
console.error(result);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
46
|
+
} else {
|
|
47
|
+
logger.error(`Deployment type "${type}" is not supported yet.`);
|
|
48
|
+
throw new Error(`Unsupported deployment type: ${type}`);
|
|
49
|
+
}
|
|
50
|
+
};
|
package/lib/server/server.js
CHANGED
|
@@ -5,6 +5,7 @@ import helmet from 'helmet';
|
|
|
5
5
|
import morgan from 'morgan';
|
|
6
6
|
import { logger } from '../logger.js';
|
|
7
7
|
import { executeCommand } from './executor.js';
|
|
8
|
+
import { getConfig } from '../config.js';
|
|
8
9
|
|
|
9
10
|
export const createServer = (secretKey, workingDir) => {
|
|
10
11
|
const app = express();
|
|
@@ -59,5 +60,31 @@ export const createServer = (secretKey, workingDir) => {
|
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
62
|
|
|
63
|
+
// Endpoint for custom deployment command
|
|
64
|
+
app.post('/deploy/custom', authenticate, async (req, res) => {
|
|
65
|
+
logger.info('Received custom deploy request');
|
|
66
|
+
|
|
67
|
+
// Get command from environment variable or config
|
|
68
|
+
const deploymentCommand = getConfig('deployment_command') || process.env.DEPLOYMENT_COMMAND;
|
|
69
|
+
|
|
70
|
+
if (!deploymentCommand) {
|
|
71
|
+
logger.error('No DEPLOYMENT_COMMAND set');
|
|
72
|
+
return res.status(400).json({
|
|
73
|
+
status: 'error',
|
|
74
|
+
error: 'No DEPLOYMENT_COMMAND configured. Set it via env var or "redep config set deployment_command <cmd>"'
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
logger.info(`Executing custom command: ${deploymentCommand}`);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const result = await executeCommand(deploymentCommand, workingDir);
|
|
82
|
+
res.json({ status: 'success', output: result });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
logger.error(`Deployment failed: ${error}`);
|
|
85
|
+
res.status(500).json({ status: 'error', error: error.message || error });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
62
89
|
return app;
|
|
63
90
|
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remote-deploy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"redep": "./bin/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
11
|
+
"update": "node scripts/update.js",
|
|
12
|
+
"format": "prettier --write .",
|
|
13
|
+
"format:check": "prettier --check .",
|
|
14
|
+
"sonar": "node sonar-project.js"
|
|
11
15
|
},
|
|
12
16
|
"keywords": [],
|
|
13
17
|
"author": "",
|
|
@@ -25,5 +29,9 @@
|
|
|
25
29
|
"helmet": "^7.1.0",
|
|
26
30
|
"morgan": "^1.10.0",
|
|
27
31
|
"pm2": "^6.0.14"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"prettier": "^3.8.0",
|
|
35
|
+
"sonarqube-scanner": "^4.3.4"
|
|
28
36
|
}
|
|
29
37
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
// Get arguments from command line
|
|
6
|
+
// Usage: node scripts/update.js <updateType> <commitMessage>
|
|
7
|
+
const updateType = process.argv[2];
|
|
8
|
+
const commitMessage = process.argv[3];
|
|
9
|
+
|
|
10
|
+
// Valid update types
|
|
11
|
+
const VALID_TYPES = ['patch', 'minor', 'major'];
|
|
12
|
+
|
|
13
|
+
// --- Validation ---
|
|
14
|
+
|
|
15
|
+
if (!updateType || !VALID_TYPES.includes(updateType)) {
|
|
16
|
+
console.error(`Error: Invalid or missing updateType. Must be one of: ${VALID_TYPES.join(', ')}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!commitMessage) {
|
|
21
|
+
console.error('Error: Missing commitMessage.');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const packageJsonPath = path.resolve('package.json');
|
|
26
|
+
|
|
27
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
28
|
+
console.error('Error: package.json not found.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- Version Update Logic ---
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
36
|
+
|
|
37
|
+
if (!packageJson.version) {
|
|
38
|
+
console.error('Error: package.json does not contain a "version" field.');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const currentVersion = packageJson.version;
|
|
43
|
+
const versionParts = currentVersion.split('.').map(Number);
|
|
44
|
+
|
|
45
|
+
if (versionParts.length !== 3 || versionParts.some(isNaN)) {
|
|
46
|
+
console.error(`Error: Invalid semantic version format in package.json: ${currentVersion}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let [major, minor, patch] = versionParts;
|
|
51
|
+
|
|
52
|
+
switch (updateType) {
|
|
53
|
+
case 'major':
|
|
54
|
+
major++;
|
|
55
|
+
minor = 0;
|
|
56
|
+
patch = 0;
|
|
57
|
+
break;
|
|
58
|
+
case 'minor':
|
|
59
|
+
minor++;
|
|
60
|
+
patch = 0;
|
|
61
|
+
break;
|
|
62
|
+
case 'patch':
|
|
63
|
+
patch++;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const newVersion = `${major}.${minor}.${patch}`;
|
|
68
|
+
packageJson.version = newVersion;
|
|
69
|
+
|
|
70
|
+
// Write updated package.json
|
|
71
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
72
|
+
console.log(`Version updated: ${currentVersion} -> ${newVersion}`);
|
|
73
|
+
|
|
74
|
+
// --- Git Operations ---
|
|
75
|
+
|
|
76
|
+
console.log('Staging changes...');
|
|
77
|
+
execSync('git add .', { stdio: 'inherit' });
|
|
78
|
+
|
|
79
|
+
console.log(`Committing with message: "${commitMessage}"...`);
|
|
80
|
+
execSync(`git commit -m "${commitMessage}"`, { stdio: 'inherit' });
|
|
81
|
+
|
|
82
|
+
console.log('Pushing to origin main...');
|
|
83
|
+
execSync('git push origin main', { stdio: 'inherit' });
|
|
84
|
+
|
|
85
|
+
console.log('Update process completed successfully!');
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`Error during update process: ${error.message}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
package/server-entry.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { startServer } from './lib/server/index.js';
|
|
2
|
+
import { logger } from './lib/logger.js';
|
|
3
|
+
import { getConfig } from './lib/config.js';
|
|
4
|
+
import 'dotenv/config';
|
|
5
|
+
|
|
6
|
+
// Dedicated entry point for PM2 to ensure clean ESM handling
|
|
7
|
+
// PM2 sometimes struggles with CLI binaries directly in ESM mode
|
|
8
|
+
|
|
9
|
+
const port = process.env.SERVER_PORT || getConfig('server_port') || 3000;
|
|
10
|
+
const secret = process.env.SECRET_KEY || getConfig('secret_key');
|
|
11
|
+
const workingDir = process.env.WORKING_DIR || getConfig('working_dir');
|
|
12
|
+
|
|
13
|
+
if (!secret) {
|
|
14
|
+
logger.warn('Warning: No "secret_key" set. Communication might be insecure.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!workingDir) {
|
|
18
|
+
logger.error('Error: "working_dir" is not set.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
startServer(port, secret, workingDir);
|
package/sonar-project.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const scanner = require('sonarqube-scanner');
|
|
2
|
+
|
|
3
|
+
scanner(
|
|
4
|
+
{
|
|
5
|
+
serverUrl: process.env.SONAR_HOST_URL || 'http://localhost:9000',
|
|
6
|
+
token: process.env.SONAR_TOKEN,
|
|
7
|
+
options: {
|
|
8
|
+
'sonar.projectKey': 'remote-deploy-cli',
|
|
9
|
+
'sonar.projectName': 'Remote Deploy CLI',
|
|
10
|
+
'sonar.projectVersion': '1.0.0',
|
|
11
|
+
'sonar.sources': 'bin,lib',
|
|
12
|
+
'sonar.tests': 'test', // Assuming tests are in a 'test' directory
|
|
13
|
+
'sonar.javascript.lcov.reportPaths': 'coverage/lcov.info',
|
|
14
|
+
'sonar.sourceEncoding': 'UTF-8',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
() => process.exit()
|
|
18
|
+
);
|