Skip to content

Commit a3d103e

Browse files
committed
Updates
1 parent 53c2dcf commit a3d103e

12 files changed

Lines changed: 237 additions & 205 deletions

File tree

package-lock.json

Lines changed: 38 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"license": "ISC",
2222
"dependencies": {
2323
"@aws-sdk/client-s3": "^3.386.0",
24+
"app-root-path": "^3.1.0",
2425
"archiver": "^5.3.1",
2526
"axios": "^1.7.9",
2627
"commander": "^11.0.0",
@@ -50,6 +51,7 @@
5051
"ts-node": "^10.9.1",
5152
"tsc-alias": "^1.8.7",
5253
"typescript": "^5.7.3",
53-
"vitest": "^2.1.8"
54+
"vitest": "^2.1.8",
55+
"vitest-mock-extended": "^2.0.2"
5456
}
5557
}

src/bin/app.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
import { Command } from 'commander';
33
import fs from 'fs';
44
import path from 'path';
5-
import shell from 'shelljs';
65

7-
import backupBootstrap from '@/bootstrap/backup.js';
8-
import repairBootstrap from '@/bootstrap/repair.js';
9-
import setupBootstrap from '@/bootstrap/setup.js';
10-
import templateBootstrap from '@/bootstrap/template.js';
6+
import BackupBootstrap from '@/bootstrap/backup.js';
7+
import RepairBootstrap from '@/bootstrap/repair.js';
8+
import SetupBootstrap from '@/bootstrap/setup.js';
9+
import TemplateBootstrap from '@/bootstrap/template.js';
1110
import DatabaseService from '@/service/database';
11+
import { isDirectory, isFile } from '@/util/path';
12+
import { isMySQLInstalled, isPHPInstalled } from '@/util/unix';
1213

1314
const program = new Command();
1415

@@ -18,7 +19,7 @@ program
1819
.version('1.0.0', '-v, --version');
1920

2021
program.command('template').action(async function () {
21-
await templateBootstrap.handler();
22+
await TemplateBootstrap.handler();
2223
});
2324

2425
program
@@ -33,10 +34,10 @@ program
3334
.requiredOption('-d, --directory <directory>', 'root directory for website', '.')
3435
.action(async (options) => {
3536
const { template: templateFilePath, directory, host, port, username, password, dev } = options;
36-
if (!fs.existsSync(templateFilePath)) {
37-
throw new Error(`File not exists in ${templateFilePath}.`);
37+
if (!isFile(templateFilePath)) {
38+
throw new Error('Please provide the correct file path.');
3839
}
39-
if (!shell.which('php')) {
40+
if (!isPHPInstalled()) {
4041
throw new Error('The host need to install php to make it works.');
4142
}
4243
if (!dev) {
@@ -45,7 +46,7 @@ program
4546
}
4647
if (
4748
(host === undefined || host === 'localhost' || host === '127.0.0.1') &&
48-
!shell.which('mysqld')
49+
!isMySQLInstalled()
4950
) {
5051
throw new Error('Using localhost or 127.0.0.1 must be installed MySQL database on host.');
5152
}
@@ -55,7 +56,7 @@ program
5556
return;
5657
}
5758
const template = JSON.parse(fs.readFileSync(templateFilePath, 'utf8'));
58-
await setupBootstrap.handler(
59+
await SetupBootstrap.handler(
5960
path.isAbsolute(directory) ? directory : path.join(process.cwd(), directory),
6061
template,
6162
dev,
@@ -74,23 +75,23 @@ program
7475
.requiredOption('-d, --directory <directory>', 'root directory for website', '.')
7576
.action(async (options) => {
7677
const { domain, directory, host, port, username, password } = options;
77-
if (!shell.which('php')) {
78+
if (!isPHPInstalled()) {
7879
throw new Error('The host need to install php to make it works.');
7980
}
8081
if (process.getuid && process.getuid() !== 0) {
8182
throw new Error('The process needs root permission when production mode.');
8283
}
8384
if (
8485
(host === undefined || host === 'localhost' || host === '127.0.0.1') &&
85-
!shell.which('mysqld')
86+
!isMySQLInstalled()
8687
) {
8788
throw new Error('Using localhost or 127.0.0.1 must be installed MySQL database on host.');
8889
}
8990
const accessible = await new DatabaseService({ host, port, username, password }).isAccessible();
9091
if (accessible) {
9192
return;
9293
}
93-
await repairBootstrap.handler(directory, domain, { host, port, username, password });
94+
await RepairBootstrap.handler(directory, domain, { host, port, username, password });
9495
});
9596

9697
program
@@ -103,13 +104,13 @@ program
103104
.requiredOption('-k, --secret-access-key <secretAccessKey>', 'AWS s3 bucket secret access key')
104105
.action(async (options) => {
105106
const { username, password, directory, accessKeyId, secretAccessKey } = options;
106-
if (!fs.existsSync(directory) || !fs.lstatSync(directory).isDirectory()) {
107+
if (!isDirectory(directory)) {
107108
throw new Error('Root directory not exists.');
108109
}
109110
if (directory === '/') {
110111
throw new Error('Cannot archive the root directory.');
111112
}
112-
await backupBootstrap.handler(directory, username, password, accessKeyId, secretAccessKey);
113+
await BackupBootstrap.handler(directory, username, password, accessKeyId, secretAccessKey);
113114
});
114115

115116
program.parseAsync();

src/bootstrap/backup.ts

Lines changed: 9 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,14 @@
11
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
2-
import archiver from 'archiver';
3-
import { spawn } from 'child_process';
42
import { format } from 'date-fns';
53
import fs from 'fs';
64
import tmp from 'tmp';
75

86
import { getWpConfigByPath } from '@/util/config';
7+
import { backup } from '@/util/database';
8+
import { zipFiles } from '@/util/zip';
99

10-
async function backupDatabase(
11-
host: string,
12-
port: number,
13-
user: string,
14-
pass: string,
15-
database: string,
16-
fileStream: fs.WriteStream
17-
) {
18-
const mysqldump = spawn('mysqldump', [
19-
'-h',
20-
host,
21-
'-P',
22-
port.toString(),
23-
'-u',
24-
user,
25-
'-p' + pass,
26-
database,
27-
]);
28-
return new Promise(function (resolve, reject) {
29-
mysqldump.stdout.on('error', reject).pipe(fileStream);
30-
31-
fileStream.on('finish', resolve);
32-
fileStream.on('error', reject);
33-
});
34-
}
35-
36-
export default {
37-
handler: async function (
10+
export default class BackupBootstrap {
11+
static async handler(
3812
rootDir: string,
3913
dbUser: string,
4014
dbPass: string,
@@ -50,40 +24,14 @@ export default {
5024
if (database === undefined) {
5125
throw new Error("wp-config.php don't contains DB_NAME parameter.");
5226
}
53-
await backupDatabase(
54-
dbHost,
55-
dbPort,
56-
dbUser,
57-
dbPass,
58-
database,
59-
fs.createWriteStream(sqlFile.name)
60-
);
27+
await backup(fs.createWriteStream(sqlFile.name), dbHost, dbPort, dbUser, dbPass, database);
28+
6129
const archiveFile = tmp.fileSync({
6230
mode: 0o600,
6331
prefix: 'wp-backup-',
6432
postfix: '.zip',
6533
});
66-
const outputStream = fs.createWriteStream(archiveFile.name);
67-
const archive = archiver('zip', {
68-
zlib: { level: 9 },
69-
});
70-
outputStream.on('close', function () {
71-
console.log(archive.pointer() + ' total bytes');
72-
console.log('archiver has been finalized and the output file descriptor has closed.');
73-
});
74-
outputStream.on('end', function () {
75-
console.log('Data has been drained');
76-
});
77-
archive.on('error', function (err) {
78-
throw err;
79-
});
80-
archive.pipe(outputStream);
81-
82-
// archive entire website into archive
83-
archive.directory(rootDir, 'website');
84-
archive.file(archiveFile.name, { name: 'dump.sql' });
85-
86-
await archive.finalize();
34+
await zipFiles(fs.createWriteStream(archiveFile.name), rootDir, archiveFile.name);
8735

8836
const client = new S3Client({
8937
credentials: {
@@ -103,5 +51,5 @@ export default {
10351
await client.send(command);
10452

10553
console.log('Finished to backup the website.');
106-
},
107-
};
54+
}
55+
}

src/bootstrap/repair.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import shell from 'shelljs';
33
import InstallationService from '@/service/installation';
44
import { MySQLCredential } from '@/type/common';
55

6-
export default {
7-
handler: async function (directory: string, domain: string, database: MySQLCredential) {
6+
export default class RepairBootstrap {
7+
static async handler(directory: string, domain: string, database: MySQLCredential) {
88
const { phpVer } = await InstallationService.setup(
99
directory,
1010
domain,
@@ -19,5 +19,5 @@ export default {
1919
if (shell.exec(`systemctl restart php${phpVer}-fpm`).code !== 0) {
2020
throw new Error('Failed to restart php service');
2121
}
22-
},
23-
};
22+
}
23+
}

src/bootstrap/setup.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import InstallationService from '@/service/installation';
88
import { MySQLCredential, WPTemplate } from '@/type/common';
99
import { unzip } from '@/util/zip';
1010

11-
export default {
12-
handler: async function (
11+
export default class SetupBootstrap {
12+
static async handler(
1313
directory: string,
1414
template: WPTemplate,
1515
isDev: boolean,
@@ -65,5 +65,5 @@ export default {
6565
if (shell.exec(`systemctl restart php${phpVer}-fpm`).code !== 0) {
6666
throw new Error('Failed to restart php service');
6767
}
68-
},
69-
};
68+
}
69+
}

src/bootstrap/template.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
import inquirer from 'inquirer';
22
import psl from 'psl';
33

4-
export default {
5-
handler: async function() {
6-
const { domain } = await inquirer.prompt({ name: 'domain', message: 'domain for the website?' });
4+
export default class TemplateBootstrap {
5+
static async handler() {
6+
const { domain } = await inquirer.prompt({
7+
name: 'domain',
8+
message: 'domain for the website?',
9+
});
710
if (!psl.isValid(domain)) {
811
throw new Error('domain is not vaild TLD inside public suffix list');
912
}
10-
const { version } = await inquirer.prompt({ name: 'version', message: 'version of wordpress?', default: 'latest' });
11-
console.log(JSON.stringify({
12-
domain,
13-
version,
14-
themes: {
15-
twentytwentythree: 'latest',
16-
},
17-
plugins: {
18-
akismet: 'latest',
19-
},
20-
}, undefined, 4));
13+
const { version } = await inquirer.prompt({
14+
name: 'version',
15+
message: 'version of wordpress?',
16+
default: 'latest',
17+
});
18+
console.log(
19+
JSON.stringify(
20+
{
21+
domain,
22+
version,
23+
themes: {
24+
twentytwentythree: 'latest',
25+
},
26+
plugins: {
27+
akismet: 'latest',
28+
},
29+
},
30+
undefined,
31+
4
32+
)
33+
);
2134
}
22-
}
35+
}

0 commit comments

Comments
 (0)