@tertium/hlpr 0.4.0 → 0.5.1
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 +1 -1
- package/bin/commands/deploy/deploy/deploy.sh +41 -0
- package/bin/commands/deploy/deploy.js +95 -0
- package/bin/commands/deploy/deploy.service.js +111 -0
- package/bin/commands/deploy/deploy.sh +41 -0
- package/bin/commands/deploy/deploy.types.js +0 -0
- package/bin/commands/help/help.js +0 -0
- package/bin/index.js +0 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Deploy application to remote server with environment variables
|
|
3
|
+
|
|
4
|
+
SKIP_BUILD="{{SKIP_BUILD}}"
|
|
5
|
+
DEPLOY_USER="{{DEPLOY_USER}}"
|
|
6
|
+
DEPLOY_HOST="{{DEPLOY_HOST}}"
|
|
7
|
+
DEPLOY_PATH="{{DEPLOY_PATH}}"
|
|
8
|
+
APP_NAME="{{APP_NAME}}"
|
|
9
|
+
|
|
10
|
+
echo "Deploying to $DEPLOY_HOST:$DEPLOY_PATH"
|
|
11
|
+
echo "=========================================="
|
|
12
|
+
|
|
13
|
+
# Build phase (unless skip-build is set)
|
|
14
|
+
if [ "$SKIP_BUILD" != "true" ]; then
|
|
15
|
+
echo "Building..."
|
|
16
|
+
bun run build
|
|
17
|
+
if [ $? -ne 0 ]; then
|
|
18
|
+
echo "✗ Build failed"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Deploy phase
|
|
24
|
+
echo ""
|
|
25
|
+
echo "Copying files to remote server..."
|
|
26
|
+
scp -r dist/ $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/
|
|
27
|
+
if [ $? -ne 0 ]; then
|
|
28
|
+
echo "✗ SCP failed"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo ""
|
|
33
|
+
echo "Installing dependencies and restarting service..."
|
|
34
|
+
ssh $DEPLOY_USER@$DEPLOY_HOST "cd $DEPLOY_PATH && bun install --production && pm2 restart $APP_NAME --update-env"
|
|
35
|
+
if [ $? -ne 0 ]; then
|
|
36
|
+
echo "✗ Remote deployment failed"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "✓ Deployment complete!"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/deploy/deploy.ts
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
9
|
+
var __dirname2 = path.dirname(__filename2);
|
|
10
|
+
function loadEnv(projectDir) {
|
|
11
|
+
const envPath = path.join(projectDir, ".env");
|
|
12
|
+
if (!existsSync(envPath)) {
|
|
13
|
+
console.error("Error: .env file not found");
|
|
14
|
+
console.error(`Please copy .env.example to .env in ${projectDir}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const env = {};
|
|
18
|
+
const content = readFileSync(envPath, "utf-8");
|
|
19
|
+
content.split(`
|
|
20
|
+
`).forEach((line) => {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
23
|
+
return;
|
|
24
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
25
|
+
if (key && valueParts.length > 0) {
|
|
26
|
+
env[key.trim()] = valueParts.join("=").trim();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
return env;
|
|
30
|
+
}
|
|
31
|
+
function validate(env) {
|
|
32
|
+
const required = ["DEPLOY_USER", "DEPLOY_HOST", "DEPLOY_PATH", "APP_NAME"];
|
|
33
|
+
const missing = required.filter((key) => !env[key]);
|
|
34
|
+
if (missing.length > 0) {
|
|
35
|
+
console.error(`Error: Missing environment variables: ${missing.join(", ")}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function executeScript(scriptContent, variables, cwd) {
|
|
40
|
+
let script = scriptContent;
|
|
41
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
42
|
+
script = script.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
43
|
+
}
|
|
44
|
+
const lines = script.split(`
|
|
45
|
+
`);
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("#!/"))
|
|
49
|
+
continue;
|
|
50
|
+
if (/^[A-Z_][A-Z0-9_]*=/.test(trimmed))
|
|
51
|
+
continue;
|
|
52
|
+
try {
|
|
53
|
+
console.log(`→ ${trimmed}`);
|
|
54
|
+
execSync(trimmed, {
|
|
55
|
+
stdio: "inherit",
|
|
56
|
+
cwd,
|
|
57
|
+
shell: true
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`
|
|
61
|
+
✗ Command failed: ${trimmed}
|
|
62
|
+
`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function deploy() {
|
|
68
|
+
const skipBuild = process.argv[2] === "skip-build";
|
|
69
|
+
const projectDir = process.cwd();
|
|
70
|
+
const env = loadEnv(projectDir);
|
|
71
|
+
validate(env);
|
|
72
|
+
const scriptPath = path.join(__dirname2, "deploy.sh");
|
|
73
|
+
if (!existsSync(scriptPath)) {
|
|
74
|
+
console.error(`Error: Deploy script not found at ${scriptPath}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const scriptContent = readFileSync(scriptPath, "utf-8");
|
|
78
|
+
const variables = {
|
|
79
|
+
...env,
|
|
80
|
+
SKIP_BUILD: skipBuild ? "true" : "false"
|
|
81
|
+
};
|
|
82
|
+
console.log(`
|
|
83
|
+
Deploying to ${env.DEPLOY_HOST}:${env.DEPLOY_PATH}`);
|
|
84
|
+
console.log(`==========================================
|
|
85
|
+
`);
|
|
86
|
+
try {
|
|
87
|
+
executeScript(scriptContent, variables, projectDir);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`
|
|
90
|
+
✗ Deployment failed: ${error.message}
|
|
91
|
+
`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
deploy();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// src/commands/deploy/deploy.service.ts
|
|
2
|
+
import { execSync } from "node:child_process";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
class DeployService {
|
|
6
|
+
config;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
log(message) {
|
|
11
|
+
console.log(`[${new Date().toISOString()}] ${message}`);
|
|
12
|
+
}
|
|
13
|
+
async copyToServer() {
|
|
14
|
+
this.log("Copying dist folder to remote server...");
|
|
15
|
+
const { remoteHost, remoteUser, deployPath, localDist, envFile } = this.config;
|
|
16
|
+
if (!fs.existsSync(localDist)) {
|
|
17
|
+
throw new Error(`Local dist folder not found: ${localDist}`);
|
|
18
|
+
}
|
|
19
|
+
const sshHost = `${remoteUser}@${remoteHost}`;
|
|
20
|
+
const tempPath = `${deployPath}_tmp`;
|
|
21
|
+
try {
|
|
22
|
+
this.log(`Creating temp directory on remote: ${tempPath}`);
|
|
23
|
+
execSync(`ssh ${sshHost} "mkdir -p ${tempPath} && rm -rf ${tempPath}/*"`, { stdio: "inherit" });
|
|
24
|
+
this.log(`Copying ${localDist} to ${sshHost}:${tempPath}/`);
|
|
25
|
+
execSync(`scp -r "${localDist}" ${sshHost}:${tempPath}/dist`, {
|
|
26
|
+
stdio: "inherit"
|
|
27
|
+
});
|
|
28
|
+
if (envFile && fs.existsSync(envFile)) {
|
|
29
|
+
this.log(`Copying ${envFile} to remote`);
|
|
30
|
+
execSync(`scp "${envFile}" ${sshHost}:${tempPath}/.env`, {
|
|
31
|
+
stdio: "inherit"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (fs.existsSync("package.json")) {
|
|
35
|
+
this.log("Copying package.json to remote");
|
|
36
|
+
execSync(`scp package.json ${sshHost}:${tempPath}/`, {
|
|
37
|
+
stdio: "inherit"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
this.log(`Moving files from temp to deploy path...`);
|
|
41
|
+
const moveCmd = `rm -rf ${deployPath}/dist && ` + `mv ${tempPath}/dist ${deployPath}/ && ` + `[ -f ${tempPath}/.env ] && mv ${tempPath}/.env ${deployPath}/.env || true && ` + `[ -f ${tempPath}/package.json ] && mv ${tempPath}/package.json ${deployPath}/ || true && ` + `rm -rf ${tempPath}`;
|
|
42
|
+
execSync(`ssh ${sshHost} "${moveCmd}"`, { stdio: "inherit" });
|
|
43
|
+
this.log("✓ Files copied successfully");
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(`Failed to copy files: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async installDependencies() {
|
|
49
|
+
if (this.config.skipInstall) {
|
|
50
|
+
this.log("Skipping dependency installation");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.log("Installing dependencies on server...");
|
|
54
|
+
const { remoteHost, remoteUser, deployPath } = this.config;
|
|
55
|
+
const sshHost = `${remoteUser}@${remoteHost}`;
|
|
56
|
+
try {
|
|
57
|
+
const cmd = `cd ${deployPath} && bun install --production`;
|
|
58
|
+
execSync(`ssh ${sshHost} "${cmd}"`, { stdio: "inherit" });
|
|
59
|
+
this.log("✓ Dependencies installed");
|
|
60
|
+
} catch (error) {
|
|
61
|
+
this.log("⚠ Dependency installation skipped or failed");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async restartProcess() {
|
|
65
|
+
if (this.config.skipRestart) {
|
|
66
|
+
this.log("Skipping process restart");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.log("Restarting application via PM2...");
|
|
70
|
+
const { remoteHost, remoteUser, deployPath, appName, port } = this.config;
|
|
71
|
+
const sshHost = `${remoteUser}@${remoteHost}`;
|
|
72
|
+
try {
|
|
73
|
+
const portOpt = port ? ` --env PORT=${port}` : "";
|
|
74
|
+
const cmd = `cd ${deployPath} && ` + `(pm2 restart ${appName} --update-env 2>/dev/null || ` + `pm2 start dist/index.js --name ${appName}${portOpt} 2>/dev/null) && ` + `pm2 save 2>/dev/null || true`;
|
|
75
|
+
execSync(`ssh ${sshHost} "bash -l -c '${cmd}'"`, { stdio: "inherit" });
|
|
76
|
+
this.log("✓ Process restart attempted");
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.log("⚠ PM2 restart skipped - may need manual setup");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async deploy() {
|
|
82
|
+
const startTime = new Date().toISOString();
|
|
83
|
+
try {
|
|
84
|
+
this.log("Starting deployment...");
|
|
85
|
+
await this.copyToServer();
|
|
86
|
+
await this.installDependencies();
|
|
87
|
+
await this.restartProcess();
|
|
88
|
+
this.log("✅ Deployment completed successfully");
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
message: "Deployment completed successfully",
|
|
92
|
+
timestamp: startTime
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
96
|
+
this.log(`❌ Deployment failed: ${message}`);
|
|
97
|
+
return {
|
|
98
|
+
success: false,
|
|
99
|
+
message,
|
|
100
|
+
timestamp: startTime
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function createDeployService(config) {
|
|
106
|
+
return new DeployService(config);
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
createDeployService,
|
|
110
|
+
DeployService
|
|
111
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# @description Deploy application to remote server with environment variables
|
|
3
|
+
|
|
4
|
+
SKIP_BUILD="{{SKIP_BUILD}}"
|
|
5
|
+
DEPLOY_USER="{{DEPLOY_USER}}"
|
|
6
|
+
DEPLOY_HOST="{{DEPLOY_HOST}}"
|
|
7
|
+
DEPLOY_PATH="{{DEPLOY_PATH}}"
|
|
8
|
+
APP_NAME="{{APP_NAME}}"
|
|
9
|
+
|
|
10
|
+
echo "Deploying to $DEPLOY_HOST:$DEPLOY_PATH"
|
|
11
|
+
echo "=========================================="
|
|
12
|
+
|
|
13
|
+
# Build phase (unless skip-build is set)
|
|
14
|
+
if [ "$SKIP_BUILD" != "true" ]; then
|
|
15
|
+
echo "Building..."
|
|
16
|
+
bun run build
|
|
17
|
+
if [ $? -ne 0 ]; then
|
|
18
|
+
echo "✗ Build failed"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Deploy phase
|
|
24
|
+
echo ""
|
|
25
|
+
echo "Copying files to remote server..."
|
|
26
|
+
scp -r dist/ $DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/
|
|
27
|
+
if [ $? -ne 0 ]; then
|
|
28
|
+
echo "✗ SCP failed"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo ""
|
|
33
|
+
echo "Installing dependencies and restarting service..."
|
|
34
|
+
ssh $DEPLOY_USER@$DEPLOY_HOST "cd $DEPLOY_PATH && bun install --production && pm2 restart $APP_NAME --update-env"
|
|
35
|
+
if [ $? -ne 0 ]; then
|
|
36
|
+
echo "✗ Remote deployment failed"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo ""
|
|
41
|
+
echo "✓ Deployment complete!"
|
|
File without changes
|
|
File without changes
|
package/bin/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tertium/hlpr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Windows and *Nix utility for typical programming activity",
|
|
5
5
|
"author": "Vitalii Balabanov",
|
|
6
6
|
"email": "tertiumnon@gmail.com",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "bun run build:main && bun run build:commands",
|
|
13
13
|
"test": "bun test",
|
|
14
14
|
"test:e2e": "node scripts/test-rename-e2e.cjs",
|
|
15
|
-
"prepublishOnly": "
|
|
15
|
+
"prepublishOnly": "bun run build",
|
|
16
16
|
"release:minor": "node node_modules/@tertium/js/scripts/release.js minor",
|
|
17
17
|
"release:patch": "node node_modules/@tertium/js/scripts/release.js patch",
|
|
18
18
|
"release:major": "node node_modules/@tertium/js/scripts/release.js major",
|