Skip to content

Commit 16a89d1

Browse files
committed
feat : Enhncaement
1 parent 22ceadb commit 16a89d1

File tree

3 files changed

+138
-81
lines changed

3 files changed

+138
-81
lines changed

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"node-ssh": "^13.2.0"
1515
},
1616
"devDependencies": {
17+
"dotenv": "^16.4.5",
1718
"jest": "^29.7.0",
1819
"nock": "^13.5.4"
1920
},

src/index.js

Lines changed: 124 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,30 @@ const core = require('@actions/core');
22
const github = require('@actions/github');
33
const { NodeSSH } = require('node-ssh');
44
const axios = require('axios');
5+
const dotenv = require('dotenv');
56

67
const ssh = new NodeSSH();
78

9+
// Load environment variables from .env file in development mode
10+
if (process.env.NODE_ENV !== 'production') {
11+
dotenv.config();
12+
}
13+
814
async function deploy() {
915
try {
1016
const inputs = getInputs();
17+
validateInputs(inputs);
1118
logInputs(inputs);
1219

1320
await checkSponsorship(inputs.githubRepoOwner);
1421

15-
await connectToServer(inputs);
22+
await sshOperations.connect(inputs);
1623

1724
await prepareDeployment(inputs);
1825

1926
await activateRelease(inputs);
2027

21-
console.log("Deployment completed successfully!");
28+
log("Deployment completed successfully!");
2229
} catch (error) {
2330
console.error(`Error: ${error.message}`);
2431
core.setFailed(error.message);
@@ -29,72 +36,121 @@ async function deploy() {
2936

3037
function getInputs() {
3138
return {
32-
host: core.getInput('host'),
33-
username: core.getInput('username'),
34-
port: core.getInput('port'),
35-
password: core.getInput('password'),
36-
sshKey: core.getInput('ssh_key'), // SSH key input
37-
target: core.getInput('target'),
38-
sha: core.getInput('sha'),
39-
githubToken: core.getInput('github_token'),
40-
envFile: core.getInput('env_file'),
41-
commandScriptBeforeCheckFolders: core.getInput('command_script_before_check_folders'),
42-
commandScriptAfterCheckFolders: core.getInput('command_script_after_check_folders'),
43-
commandScriptBeforeDownload: core.getInput('command_script_before_download'),
44-
commandScriptAfterDownload: core.getInput('command_script_after_download'),
45-
commandScriptBeforeActivate: core.getInput('command_script_before_activate'),
46-
commandScriptAfterActivate: core.getInput('command_script_after_activate'),
47-
githubRepoOwner: github.context.payload.repository.owner.login,
48-
githubRepo: github.context.payload.repository.name
39+
host: process.env.HOST || core.getInput('host'),
40+
username: process.env.REMOTE_USERNAME || core.getInput('username'),
41+
port: process.env.PORT || core.getInput('port'),
42+
password: process.env.PASSWORD || core.getInput('password'),
43+
sshKey: (process.env.SSH_KEY || core.getInput('ssh_key')).replace(/\\n/g, '\n'), // Replace escaped newlines with actual newlines
44+
passphrase: process.env.SSH_PASSPHRASE || core.getInput('ssh_passphrase'), // Add passphrase
45+
target: process.env.TARGET || core.getInput('target'),
46+
sha: process.env.SHA || core.getInput('sha'),
47+
deploy_branch: process.env.GITHUB_DEPLOY_BRANCH || core.getInput('deploy_branch'),
48+
githubToken: process.env.GITHUB_TOKEN || core.getInput('github_token'),
49+
envFile: process.env.ENV_FILE || core.getInput('env_file'),
50+
commandScriptBeforeCheckFolders: process.env.COMMAND_SCRIPT_BEFORE_CHECK_FOLDERS || core.getInput('command_script_before_check_folders'),
51+
commandScriptAfterCheckFolders: process.env.COMMAND_SCRIPT_AFTER_CHECK_FOLDERS || core.getInput('command_script_after_check_folders'),
52+
commandScriptBeforeDownload: process.env.COMMAND_SCRIPT_BEFORE_DOWNLOAD || core.getInput('command_script_before_download'),
53+
commandScriptAfterDownload: process.env.COMMAND_SCRIPT_AFTER_DOWNLOAD || core.getInput('command_script_after_download'),
54+
commandScriptBeforeActivate: process.env.COMMAND_SCRIPT_BEFORE_ACTIVATE || core.getInput('command_script_before_activate'),
55+
commandScriptAfterActivate: process.env.COMMAND_SCRIPT_AFTER_ACTIVATE || core.getInput('command_script_after_activate'),
56+
githubRepoOwner: process.env.GITHUB_REPO_OWNER || github.context.payload.repository.owner.login,
57+
githubRepo: process.env.GITHUB_REPO || github.context.payload.repository.name
4958
};
5059
}
5160

61+
function validateInputs(inputs) {
62+
if (!inputs.host) throw new Error("Host is required.");
63+
if (!inputs.username) throw new Error("Username is required.");
64+
if (inputs.port && (isNaN(inputs.port) || inputs.port < 1 || inputs.port > 65535)) {
65+
throw new Error("Port must be a valid number between 1 and 65535.");
66+
}
67+
if (!inputs.target) throw new Error("Target directory is required.");
68+
if (!inputs.sha) throw new Error("SHA is required.");
69+
if (!inputs.githubToken) throw new Error("GitHub token is required.");
70+
}
71+
72+
function log(message) {
73+
const timestamp = new Date().toISOString();
74+
console.log(`[${timestamp}] ${message}`);
75+
}
76+
5277
function logInputs(inputs) {
53-
console.log(`Host: ${inputs.host}`);
54-
console.log(`Target: ${inputs.target}`);
55-
console.log(`SHA: ${inputs.sha}`);
56-
console.log(`GitHub Repo Owner: ${inputs.githubRepoOwner}`);
78+
log(`Host: ${inputs.host}`);
79+
log(`Target: ${inputs.target}`);
80+
log(`SHA: ${inputs.sha}`);
81+
log(`GitHub Repo Owner: ${inputs.githubRepoOwner}`);
82+
// Omitting sensitive information like GitHub token and SSH key
5783
}
5884

5985
async function checkSponsorship(githubRepoOwner) {
6086
try {
6187
const response = await axios.post('https://deployer.flowsahl.com/api/check-github-sponsorship', {
6288
github_username: githubRepoOwner
6389
});
64-
console.log('Thanks for sponsoring us :)');
90+
log('Thanks for sponsoring us :)');
6591
} catch (error) {
6692
handleSponsorshipError(error);
6793
}
6894
}
6995

7096
function handleSponsorshipError(error) {
71-
if (error.response && error.response.status === 403) {
72-
throw new Error("You are not a sponsor, Please consider sponsoring us to use this action, https://github.com/sponsors/FlowSahl , Start sponsoring us and try again [1$ or more]");
73-
} else if (error.response && error.response.status === 500) {
74-
console.error("An error occurred while checking sponsorship, but the deployment will continue.");
97+
if (error.response) {
98+
if (error.response.status === 403) {
99+
throw new Error("You are not a sponsor. Please consider sponsoring us to use this action: https://github.com/sponsors/FlowSahl. Start sponsoring us and try again [1$ or more].");
100+
} else if (error.response.status === 500) {
101+
log("An internal server error occurred while checking sponsorship, but the deployment will continue.");
102+
} else {
103+
log(`Sponsorship check failed with status ${error.response.status}: ${error.response.data}`);
104+
throw new Error("Sponsorship check failed. Please try again later.");
105+
}
75106
} else {
76-
throw error;
107+
log("An unknown error occurred during the sponsorship check.");
108+
// throw error;
77109
}
78110
}
79111

80-
async function connectToServer({ host, username, port, password, sshKey }) {
81-
console.log("Connecting to the server...");
82-
const connectionOptions = {
83-
host,
84-
username,
85-
port: port ? parseInt(port) : undefined,
86-
};
87-
88-
if (sshKey) {
89-
connectionOptions.privateKey = sshKey;
90-
} else if (password) {
91-
connectionOptions.password = password;
92-
} else {
93-
throw new Error("Either an SSH key or a password must be provided for the SSH connection.");
112+
const sshOperations = {
113+
async connect({ host, username, port, password, sshKey, passphrase }) {
114+
log("Connecting to the server...");
115+
const connectionOptions = {
116+
host,
117+
username,
118+
port: port ? parseInt(port) : undefined,
119+
privateKey: sshKey,
120+
passphrase: passphrase
121+
};
122+
123+
try {
124+
await ssh.connect(connectionOptions);
125+
log("Successfully connected using SSH key.");
126+
} catch (keyError) {
127+
if (password) {
128+
log("SSH key authentication failed, attempting password authentication...");
129+
try {
130+
await ssh.connect({ host, username, port: port ? parseInt(port) : undefined, password });
131+
log("Successfully connected using password.");
132+
} catch (passwordError) {
133+
log(`Failed to connect with password: ${passwordError.message}`);
134+
throw passwordError;
135+
}
136+
} else {
137+
log(`Failed to connect with SSH key: ${keyError.message}`);
138+
throw keyError;
139+
}
140+
}
141+
},
142+
143+
async execute(command) {
144+
try {
145+
const result = await ssh.execCommand(command);
146+
if (result.stdout) log(result.stdout);
147+
if (result.stderr) console.error(result.stderr);
148+
if (result.code !== 0) throw new Error(`Command failed: ${command} - ${result.stderr}`);
149+
} catch (error) {
150+
throw new Error(`Failed to execute command: ${command} - ${error.message}`);
151+
}
94152
}
95-
96-
await ssh.connect(connectionOptions);
97-
}
153+
};
98154

99155
async function prepareDeployment(inputs) {
100156
const paths = getPaths(inputs.target, inputs.sha);
@@ -123,13 +179,13 @@ function getPaths(target, sha) {
123179

124180
async function runOptionalScript(script, description) {
125181
if (script !== 'false') {
126-
console.log(`Running script ${description}: ${script}`);
127-
await executeCommand(script);
182+
log(`Running script ${description}: ${script}`);
183+
await sshOperations.execute(script);
128184
}
129185
}
130186

131187
async function checkAndPrepareFolders(paths) {
132-
console.log("Checking the folders...");
188+
log("Checking the folders...");
133189
const folders = [
134190
`${paths.target}/releases`,
135191
`${paths.target}/storage`,
@@ -141,63 +197,50 @@ async function checkAndPrepareFolders(paths) {
141197
`${paths.target}/storage/framework/sessions`,
142198
`${paths.target}/storage/framework/views`
143199
];
144-
await executeCommand(`mkdir -p ${folders.join(' ')}`);
145-
await executeCommand(`rm -rf ${paths.target}/_temp_${paths.sha}`);
146-
await executeCommand(`rm -rf ${paths.target}/releases/${paths.sha}`);
147-
await executeCommand(`rm -rf ${paths.target}/${paths.sha}.zip`);
200+
await sshOperations.execute(`mkdir -p ${folders.join(' ')}`);
201+
await sshOperations.execute(`rm -rf ${paths.target}/_temp_${paths.sha}`);
202+
await sshOperations.execute(`rm -rf ${paths.target}/releases/${paths.sha}`);
203+
await sshOperations.execute(`rm -rf ${paths.target}/${paths.sha}.zip`);
148204
}
149205

150206
async function cloneAndPrepareRepository(inputs, paths) {
151-
if (inputs.commandScriptBeforeDownload !== 'false') {
152-
await runOptionalScript(inputs.commandScriptBeforeDownload, "before clone");
153-
}
207+
await runOptionalScript(inputs.commandScriptBeforeDownload, "before clone");
154208

155-
const repoUrl = `https://${inputs.githubRepoOwner}:${inputs.githubToken}@github.com/${inputs.githubRepoOwner}/${inputs.githubRepo}.git`;
156-
console.log(`Cloning Repo: ${repoUrl}`);
209+
const repoUrl = `git@github.com:${inputs.githubRepoOwner}/${inputs.githubRepo}.git`;
210+
log(`Cloning Repo: ${repoUrl}`);
157211

158-
await executeCommand(`
212+
await sshOperations.execute(`
159213
cd ${inputs.target} &&
214+
rm -rf ${paths.releasePath} &&
160215
git clone ${repoUrl} ${paths.releasePath} &&
161216
cd ${paths.releasePath} &&
162-
git checkout ${inputs.sha}
217+
git checkout ${inputs.deploy_branch}
163218
`);
164219
}
165220

166221
async function syncEnvironmentFile(envFile, paths) {
167222
if (envFile) {
168-
console.log("Syncing .env file");
169-
await executeCommand(`echo '${envFile}' > ${paths.target}/.env`);
170-
await executeCommand(`ln -sfn ${paths.target}/.env ${paths.releasePath}/.env`);
223+
log("Syncing .env file");
224+
await sshOperations.execute(`echo '${envFile}' > ${paths.target}/.env`);
225+
await sshOperations.execute(`ln -sfn ${paths.target}/.env ${paths.releasePath}/.env`);
171226
}
172227
}
173228

174229
async function linkStorage(paths) {
175-
console.log("Linking the current release with storage");
176-
await executeCommand(`ln -sfn ${paths.target}/storage ${paths.releasePath}/storage`);
230+
log("Linking the current release with storage");
231+
await sshOperations.execute(`ln -sfn ${paths.target}/storage ${paths.releasePath}/storage`);
177232
}
178233

179234
async function activateRelease(inputs) {
180235
const paths = getPaths(inputs.target, inputs.sha);
181236

182-
if (inputs.commandScriptBeforeActivate !== 'false') {
183-
await runOptionalScript(inputs.commandScriptBeforeActivate, "before activate");
184-
}
237+
await runOptionalScript(inputs.commandScriptBeforeActivate, "before activate");
185238

186-
console.log("Activating the release");
187-
await executeCommand(`ln -sfn ${paths.releasePath} ${paths.activeReleasePath} && ls -1dt ${inputs.target}/releases/*/ | tail -n +4 | xargs rm -rf`);
239+
log("Activating the release");
240+
await sshOperations.execute(`ln -sfn ${paths.releasePath} ${paths.activeReleasePath} && ls -1dt ${inputs.target}/releases/*/ | tail -n +4 | xargs rm -rf`);
188241

189-
if (inputs.commandScriptAfterActivate !== 'false') {
190-
await runOptionalScript(inputs.commandScriptAfterActivate, "after activate");
191-
}
192-
}
193-
194-
async function executeCommand(command) {
195-
const result = await ssh.execCommand(command);
196-
if (result.stdout) console.log(result.stdout);
197-
if (result.stderr) console.error(result.stderr);
198-
if (result.code !== 0) throw new Error(`Command failed: ${command} - ${result.stderr}`);
242+
await runOptionalScript(inputs.commandScriptAfterActivate, "after activate");
199243
}
200-
201244
module.exports = { deploy };
202245

203246
// Automatically run the deploy function when the script is executed

0 commit comments

Comments
 (0)