@frakjs/frak 0.1.1 → 0.3.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/.prettierrc +8 -0
- package/README.md +11 -15
- package/bin/frak.js +81 -55
- package/images/logos/Frak Logo - Dark - 256.png +0 -0
- package/images/logos/Frak Logo - Green - 256.png +0 -0
- package/images/logos/Frak Logo - Green - Circle.png +0 -0
- package/images/logos/Frak Logo - Light - 256.png +0 -0
- package/package.json +15 -3
- package/src/ansi.js +1 -1
- package/src/cli.js +19 -13
- package/src/config.js +41 -10
- package/src/date.js +7 -7
- package/src/diff.js +91 -61
- package/src/humanize.js +1 -1
- package/src/rsync.js +43 -22
- package/src/ssh.js +3 -1
- package/.frakignore +0 -0
- package/eslint.config.js +0 -1
- package/frak.config.js +0 -10
- /package/images/{frak-init-screenshot.png → screenshots/frak-init-screenshot.png} +0 -0
- /package/images/{node-npm-version.png → screenshots/node-npm-version.png} +0 -0
- /package/images/{screenshot.png → screenshots/screenshot.png} +0 -0
package/.prettierrc
ADDED
package/README.md
CHANGED
|
@@ -6,19 +6,18 @@
|
|
|
6
6
|
[![Stargazers][stars-shield]][stars-url]
|
|
7
7
|
[![Issues][issues-shield]][issues-url]
|
|
8
8
|
[![MIT][license-shield]][license-url]
|
|
9
|
-
[![LinkedIn][linkedin-shield]][linkedin-url]
|
|
10
9
|
|
|
11
10
|
<!-- PROJECT LOGO -->
|
|
12
11
|
<br />
|
|
13
12
|
<div align="center">
|
|
14
13
|
<a href="https://github.com/frakjs/frak">
|
|
15
|
-
<
|
|
14
|
+
<img src="images/logos/Frak Logo - Green - 256.png" width="112" />
|
|
16
15
|
</a>
|
|
17
16
|
|
|
18
17
|
<h3 align="center">frak</h3>
|
|
19
18
|
|
|
20
19
|
<p align="center">
|
|
21
|
-
|
|
20
|
+
Deploy from point A → point B, without the drama.
|
|
22
21
|
<br />
|
|
23
22
|
<br />
|
|
24
23
|
<a href="https://github.com/frakjs/frak/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
|
|
@@ -38,7 +37,6 @@
|
|
|
38
37
|
<a href="#getting-started">Getting Started</a>
|
|
39
38
|
<ul>
|
|
40
39
|
<li><a href="#prerequisites">Prerequisites</a></li>
|
|
41
|
-
<li><a href="#installation">Installation</a></li>
|
|
42
40
|
</ul>
|
|
43
41
|
</li>
|
|
44
42
|
<li><a href="#usage">Usage</a></li>
|
|
@@ -46,7 +44,6 @@
|
|
|
46
44
|
<li><a href="#contributing">Contributing</a></li>
|
|
47
45
|
<li><a href="#license">License</a></li>
|
|
48
46
|
<li><a href="#contact">Contact</a></li>
|
|
49
|
-
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
|
50
47
|
</ol>
|
|
51
48
|
</details>
|
|
52
49
|
|
|
@@ -57,9 +54,9 @@
|
|
|
57
54
|
|
|
58
55
|
[![Product Name Screen Shot][product-screenshot]](https://frakjs.com)
|
|
59
56
|
|
|
60
|
-
**`frak`** is
|
|
57
|
+
**`frak`** is a tiny JavaScript binary for deployments that don’t need containers or repos—just fast, reliable file transfer from point A to point B.
|
|
61
58
|
|
|
62
|
-
|
|
59
|
+
It leverages `rsync` over SSH to push files to a remote server, and is able to visualize file changes in a developer-friendly way. **`frak`** has a number of advanced features beyond syncing files to a remote server, such as keeping a history of deployments for easy rollback, showing file differences prior to pushing, and executing post-deploy hooks.
|
|
63
60
|
|
|
64
61
|
### 🚀 Features
|
|
65
62
|
|
|
@@ -76,7 +73,7 @@
|
|
|
76
73
|
<!-- GETTING STARTED -->
|
|
77
74
|
## Getting Started
|
|
78
75
|
|
|
79
|
-
Using frak is simple. All you need is a [Node.js](https://nodejs.org) environment with `npx`.
|
|
76
|
+
Using **`frak`** is simple. All you need is a [Node.js](https://nodejs.org) environment with `npx`.
|
|
80
77
|
|
|
81
78
|
### Prerequisites
|
|
82
79
|
|
|
@@ -95,7 +92,7 @@ npx --version
|
|
|
95
92
|
<!-- USAGE EXAMPLES -->
|
|
96
93
|
## Usage
|
|
97
94
|
|
|
98
|
-
The recommended way to run
|
|
95
|
+
The recommended way to run **`frak`** is via the `npx` command.
|
|
99
96
|
|
|
100
97
|
```bash
|
|
101
98
|
npx @frakjs/frak --help
|
|
@@ -189,7 +186,7 @@ Distributed under the MIT License. See `LICENSE.txt` for more information.
|
|
|
189
186
|
<!-- CONTACT -->
|
|
190
187
|
## Contact
|
|
191
188
|
|
|
192
|
-
|
|
189
|
+
Frank Strube - [@strube](https://x.com/strube) - contact@frakjs.com
|
|
193
190
|
|
|
194
191
|
Project Link: [https://github.com/frakjs/frak](https://github.com/frakjs/frak)
|
|
195
192
|
|
|
@@ -207,8 +204,7 @@ Project Link: [https://github.com/frakjs/frak](https://github.com/frakjs/frak)
|
|
|
207
204
|
[issues-url]: https://github.com/frakjs/frak/issues
|
|
208
205
|
[license-shield]: https://img.shields.io/github/license/frakjs/frak.svg?style=for-the-badge
|
|
209
206
|
[license-url]: https://github.com/frakjs/frak/blob/master/LICENSE.txt
|
|
210
|
-
[
|
|
211
|
-
[
|
|
212
|
-
[
|
|
213
|
-
[
|
|
214
|
-
[frak-init-screenshot]: images/frak-init-screenshot.png
|
|
207
|
+
[green-logo]: images/logos/Frak%20Logo%20-%20Green%20-%20256.png
|
|
208
|
+
[product-screenshot]: images/screenshots/screenshot.png
|
|
209
|
+
[prerequisites-screenshot]: images/screenshots/node-npm-version.png
|
|
210
|
+
[frak-init-screenshot]: images/screenshots/frak-init-screenshot.png
|
package/bin/frak.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { execSync, spawn } from 'node:child_process';
|
|
4
|
-
import {
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { writeFileSync, existsSync } from 'node:fs';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
import * as ansi from '../src/ansi.js';
|
|
8
7
|
import * as cli from '../src/cli.js';
|
|
@@ -15,23 +14,17 @@ import * as rsync from '../src/rsync.js';
|
|
|
15
14
|
import * as ssh from '../src/ssh.js';
|
|
16
15
|
import * as date from '../src/date.js';
|
|
17
16
|
|
|
18
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
|
|
20
17
|
/**
|
|
21
18
|
* Try to load config. If it fails, print error message in red.
|
|
22
19
|
*
|
|
23
20
|
* @param {String} env
|
|
24
21
|
*/
|
|
25
22
|
async function tryLoadConfig(env) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch (e) {
|
|
29
|
-
console.log(ansi.red(e.message));
|
|
23
|
+
return await loadConfig(env).catch((e) => {
|
|
24
|
+
console.error(ansi.red(e.message));
|
|
30
25
|
|
|
31
26
|
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return;
|
|
27
|
+
});
|
|
35
28
|
}
|
|
36
29
|
|
|
37
30
|
/**
|
|
@@ -58,21 +51,22 @@ async function main(args) {
|
|
|
58
51
|
|
|
59
52
|
debug('parsed:', { command, options });
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
// Initialize Frak in the current directory
|
|
63
54
|
if (command === 'init') {
|
|
55
|
+
// Initialize Frak in the current directory
|
|
64
56
|
const configPath = path.join(process.cwd(), 'frak.config.js');
|
|
65
57
|
|
|
66
58
|
if (!existsSync(configPath)) {
|
|
67
|
-
writeFileSync(
|
|
59
|
+
writeFileSync(
|
|
60
|
+
configPath,
|
|
61
|
+
`export default ${JSON.stringify({ server: 'example.com', root: '/var/www/html' }, null, 4)}`
|
|
62
|
+
);
|
|
68
63
|
console.info(ansi.green('Configuration file created: frak.config.js'));
|
|
69
64
|
} else {
|
|
70
65
|
console.error(ansi.red('Frak is already initialized in this directory.'));
|
|
71
66
|
}
|
|
72
|
-
|
|
73
|
-
// Start a console session, or run the provided command on
|
|
74
|
-
// the remote server.
|
|
75
67
|
} else if (command === 'console') {
|
|
68
|
+
// Start a console session, or run the provided command on
|
|
69
|
+
// the remote server.
|
|
76
70
|
await tryLoadConfig(options.env);
|
|
77
71
|
|
|
78
72
|
if (!config.server) {
|
|
@@ -81,48 +75,65 @@ async function main(args) {
|
|
|
81
75
|
}
|
|
82
76
|
|
|
83
77
|
try {
|
|
84
|
-
const cmd = options.command
|
|
78
|
+
const cmd = options.command || '\\$SHELL -l -i';
|
|
85
79
|
|
|
86
|
-
execSync(`ssh -t ${config.server} -- cd ${config.root} \\&\\& ${cmd}`, {
|
|
80
|
+
execSync(`ssh -t ${config.server} -- cd ${config.root} \\&\\& ${cmd}`, {
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
});
|
|
87
83
|
} catch (e) {
|
|
88
|
-
debug(e)
|
|
84
|
+
debug(e);
|
|
89
85
|
}
|
|
90
|
-
// Show the differences between local files and remote server
|
|
91
86
|
} else if (command === 'diff') {
|
|
87
|
+
// Show the differences between local files and remote server
|
|
92
88
|
await tryLoadConfig(options.env);
|
|
93
89
|
|
|
94
|
-
|
|
90
|
+
try {
|
|
91
|
+
const { output } = await rsync.exec('--dry-run');
|
|
92
|
+
|
|
93
|
+
debug({ output });
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
await diff(output, options);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
debug(e);
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
console.error(e.message);
|
|
100
|
+
}
|
|
99
101
|
} else if (command === 'push' || command === 'pull') {
|
|
102
|
+
// Push or pull changes to remote server
|
|
100
103
|
await tryLoadConfig(options.env);
|
|
101
104
|
|
|
102
105
|
// Execute dry-run
|
|
103
106
|
const { output } = await rsync.exec('--dry-run');
|
|
104
107
|
|
|
108
|
+
debug({ output });
|
|
109
|
+
|
|
105
110
|
console.log(humanize(output));
|
|
106
111
|
|
|
107
112
|
if (command === 'push' && config.after) {
|
|
108
|
-
console.log(ansi.blue('Run command:'), config.after
|
|
113
|
+
console.log(ansi.blue('Run command:'), config.after);
|
|
114
|
+
console.log('');
|
|
109
115
|
}
|
|
110
116
|
|
|
111
|
-
const proceed = await cli.agree(
|
|
117
|
+
const proceed = await cli.agree(
|
|
118
|
+
ansi.yellow('The above actions will be taken. Continue? (This cannot be undone): ')
|
|
119
|
+
);
|
|
112
120
|
|
|
113
121
|
if (!proceed) {
|
|
114
122
|
process.exit(1);
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
// Prepare patch file
|
|
118
|
-
let patch;
|
|
126
|
+
let patch = '';
|
|
119
127
|
|
|
120
|
-
|
|
121
|
-
patch = await diff(output, { interactive: false })
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
if (command === 'push') {
|
|
129
|
+
patch = await diff(output, { interactive: false })
|
|
130
|
+
/* node:coverage disable */
|
|
131
|
+
.catch((e) => {
|
|
132
|
+
console.error(e);
|
|
133
|
+
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
|
136
|
+
/* node:coverage enable */
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
// Execute actual rsync command
|
|
@@ -132,12 +143,18 @@ async function main(args) {
|
|
|
132
143
|
console.log(humanize(actualOutput));
|
|
133
144
|
|
|
134
145
|
// Place patch file in backup dir
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
if (command === 'push') {
|
|
147
|
+
const hash = sha256(patch).slice(0, 7);
|
|
148
|
+
const copy = spawn('ssh', [
|
|
149
|
+
config.server,
|
|
150
|
+
'--',
|
|
151
|
+
`cat > '${config.root || '.'}/${backupPath}/${hash}.patch'`,
|
|
152
|
+
]);
|
|
137
153
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
copy.stdout.on('data', (data) => debug('' + data));
|
|
155
|
+
copy.stderr.on('data', (data) => debug('' + data));
|
|
156
|
+
copy.stdin.write(patch);
|
|
157
|
+
}
|
|
141
158
|
|
|
142
159
|
// Execute "after" command
|
|
143
160
|
if (command === 'push' && config.after) {
|
|
@@ -145,11 +162,12 @@ async function main(args) {
|
|
|
145
162
|
|
|
146
163
|
debug({ command: config.after });
|
|
147
164
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
165
|
+
await ssh
|
|
166
|
+
.exec(config.after, {
|
|
167
|
+
server: config.server,
|
|
168
|
+
root: config.root,
|
|
169
|
+
})
|
|
170
|
+
.catch(debug);
|
|
153
171
|
}
|
|
154
172
|
|
|
155
173
|
// TODO Webhook
|
|
@@ -164,13 +182,16 @@ async function main(args) {
|
|
|
164
182
|
const grep = {
|
|
165
183
|
additions: `grep -e '^+' "$NAME"/*.patch | grep -v '^+++' | wc -l`,
|
|
166
184
|
deletions: `grep -e '^-' "$NAME"/*.patch | grep -v '^---' | wc -l`,
|
|
167
|
-
}
|
|
185
|
+
};
|
|
168
186
|
const ls = `stat --format='%Y %n' .backups/* | sort -r`;
|
|
169
187
|
|
|
170
|
-
const list = ssh.exec(
|
|
188
|
+
const list = ssh.exec(
|
|
189
|
+
`${ls} | xargs -I {} sh -c 'TIMESTAMP=$(echo {} | cut -d" " -f1); NAME=$(echo {} | cut -d" " -f2-); echo $TIMESTAMP${delimeter}$NAME${delimeter}$(${grep.additions})${delimeter}$(${grep.deletions})'`,
|
|
190
|
+
{ quiet: true, server: config.server, root: config.root }
|
|
191
|
+
);
|
|
171
192
|
let backups = '';
|
|
172
193
|
|
|
173
|
-
list.stdout.on('data', data => backups += data);
|
|
194
|
+
list.stdout.on('data', (data) => (backups += data));
|
|
174
195
|
|
|
175
196
|
await list;
|
|
176
197
|
|
|
@@ -186,17 +207,22 @@ async function main(args) {
|
|
|
186
207
|
{
|
|
187
208
|
header: 'Changes',
|
|
188
209
|
width: 30,
|
|
189
|
-
}
|
|
210
|
+
},
|
|
190
211
|
]);
|
|
191
212
|
|
|
192
|
-
backups
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
213
|
+
backups
|
|
214
|
+
.trim()
|
|
215
|
+
.split(/\n/)
|
|
216
|
+
.forEach((backup) => {
|
|
217
|
+
const [timestamp, name, additions, deletions] = backup.split(delimeter);
|
|
218
|
+
table.row([
|
|
219
|
+
name.replace(/^.backups\//, ''),
|
|
220
|
+
date.relative(timestamp * 1000),
|
|
221
|
+
ansi.green(`${additions} addition${additions == 1 ? '' : 's'}`) +
|
|
222
|
+
', ' +
|
|
223
|
+
ansi.red(`${deletions} deletion${deletions == 1 ? '' : 's'}`),
|
|
224
|
+
]);
|
|
225
|
+
});
|
|
200
226
|
|
|
201
227
|
table.print();
|
|
202
228
|
} else {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frakjs/frak",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A commandline deployment tool for syncing files to remote servers.",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=12"
|
|
8
|
+
},
|
|
6
9
|
"bin": {
|
|
7
10
|
"frak": "bin/frak.js"
|
|
8
11
|
},
|
|
9
12
|
"type": "module",
|
|
10
13
|
"scripts": {
|
|
11
|
-
"
|
|
14
|
+
"eslint": "eslint",
|
|
15
|
+
"test": "node --experimental-test-coverage tests/index.test.js"
|
|
12
16
|
},
|
|
13
17
|
"repository": {
|
|
14
18
|
"type": "git",
|
|
@@ -24,5 +28,13 @@
|
|
|
24
28
|
"bugs": {
|
|
25
29
|
"url": "https://github.com/frakjs/frak/issues"
|
|
26
30
|
},
|
|
27
|
-
"homepage": "https://github.com/frakjs/frak#readme"
|
|
31
|
+
"homepage": "https://github.com/frakjs/frak#readme",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@eslint/js": "^9.33.0",
|
|
34
|
+
"eslint": "^9.33.0",
|
|
35
|
+
"globals": "^16.3.0"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"yaml": "^1.10.0"
|
|
39
|
+
}
|
|
28
40
|
}
|
package/src/ansi.js
CHANGED
|
@@ -15,7 +15,7 @@ const ansi = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export function strip(string) {
|
|
18
|
-
return string
|
|
18
|
+
return `${string}`
|
|
19
19
|
.replace(new RegExp(ansi.CLEAR.replace('[', '\\['), 'g'), '')
|
|
20
20
|
.replace(new RegExp(ansi.BOLD.replace('[', '\\['), 'g'), '')
|
|
21
21
|
.replace(new RegExp(ansi.UNDERLINE.replace('[', '\\['), 'g'), '')
|
package/src/cli.js
CHANGED
|
@@ -13,7 +13,11 @@ export function parse(args) {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
for (let arg of args) {
|
|
16
|
-
if (arg
|
|
16
|
+
if (arg === '--yes' || arg === '-y') {
|
|
17
|
+
options.interactive = false;
|
|
18
|
+
} else if (arg === '--no-color') {
|
|
19
|
+
options.colorize = false;
|
|
20
|
+
} else if (arg.includes('=')) {
|
|
17
21
|
const [option, value] = arg.split('=', 2);
|
|
18
22
|
|
|
19
23
|
options[option] = value;
|
|
@@ -25,11 +29,14 @@ export function parse(args) {
|
|
|
25
29
|
return { command, options };
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
export async function agree(string) {
|
|
29
|
-
process.stdout
|
|
32
|
+
export async function agree(string, { stdin = null, stdout = null } = {}) {
|
|
33
|
+
stdout = stdout || process.stdout;
|
|
34
|
+
stdin = stdin || process.stdin;
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
stdout.write(string);
|
|
37
|
+
|
|
38
|
+
const answer = await new Promise((resolve) => {
|
|
39
|
+
stdin.on('data', (data) => resolve(data));
|
|
33
40
|
});
|
|
34
41
|
|
|
35
42
|
return ['y', 'yes'].includes(`${answer}`.trim().toLowerCase());
|
|
@@ -49,8 +56,8 @@ export class Table {
|
|
|
49
56
|
const { border, header } = this.columns.reduce(({ header, border }, column, i) => {
|
|
50
57
|
const width = ansi.strip(column.header).length;
|
|
51
58
|
const padding = Math.max(0, column.width - width - 2);
|
|
52
|
-
const paddingString =
|
|
53
|
-
const borderString =
|
|
59
|
+
const paddingString = new Array(padding + 1).join(' ');
|
|
60
|
+
const borderString = new Array(column.width + 1).join('-');
|
|
54
61
|
|
|
55
62
|
return {
|
|
56
63
|
border: `${border || ''}+${borderString}${i === this.columns.length - 1 ? '+' : ''}`,
|
|
@@ -62,18 +69,17 @@ export class Table {
|
|
|
62
69
|
console.log(header);
|
|
63
70
|
console.log(ansi.gray(border));
|
|
64
71
|
|
|
65
|
-
this.rows.forEach(r => {
|
|
72
|
+
this.rows.forEach((r) => {
|
|
66
73
|
const rowString = r.reduce((row, cell, i) => {
|
|
67
|
-
const width = ansi.strip(cell).length
|
|
74
|
+
const width = ansi.strip(cell).length;
|
|
68
75
|
const padding = Math.max(0, this.columns[i].width - width - 2);
|
|
69
|
-
const paddingString =
|
|
70
|
-
const borderString = (new Array(this.columns[i].width + 1).join('-'));
|
|
76
|
+
const paddingString = new Array(padding + 1).join(' ');
|
|
71
77
|
|
|
72
|
-
return `${row || ''}${ansi.gray('|')} ${cell}${paddingString} ${i === r.length - 1 ? ansi.gray('|') : ''}
|
|
78
|
+
return `${row || ''}${ansi.gray('|')} ${cell}${paddingString} ${i === r.length - 1 ? ansi.gray('|') : ''}`;
|
|
73
79
|
}, '');
|
|
74
80
|
|
|
75
81
|
console.log(rowString);
|
|
76
82
|
console.log(ansi.gray(border));
|
|
77
|
-
})
|
|
83
|
+
});
|
|
78
84
|
}
|
|
79
85
|
}
|
package/src/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import * as yaml from 'yaml';
|
|
3
4
|
|
|
4
5
|
// Cached config object. The config is stored in here so it can
|
|
5
6
|
// be loaded once and imported across all modules.
|
|
@@ -13,10 +14,15 @@ let config = {};
|
|
|
13
14
|
export function locate() {
|
|
14
15
|
for (let dir = process.cwd(); dir != '/'; dir = path.dirname(dir)) {
|
|
15
16
|
const configPath = path.join(dir, 'frak.config.js');
|
|
17
|
+
const yamlPath = path.join(dir, '.frak');
|
|
16
18
|
|
|
17
19
|
if (fs.existsSync(configPath)) {
|
|
18
20
|
return configPath;
|
|
19
21
|
}
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(yamlPath)) {
|
|
24
|
+
return yamlPath;
|
|
25
|
+
}
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
return null;
|
|
@@ -30,22 +36,47 @@ export function locate() {
|
|
|
30
36
|
export async function load(env = null) {
|
|
31
37
|
const configPath = locate();
|
|
32
38
|
|
|
33
|
-
if (fs.existsSync(configPath)) {
|
|
39
|
+
if (!fs.existsSync(configPath)) {
|
|
40
|
+
throw new Error('Config file not found! Try running frak init.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (path.basename(configPath) === 'frak.config.js') {
|
|
34
44
|
Object.assign(config, (await import(configPath)).default);
|
|
45
|
+
} else if (path.basename(configPath) === '.frak') {
|
|
46
|
+
Object.assign(config, yaml.parse(fs.readFileSync(configPath).toString('utf-8')));
|
|
47
|
+
}
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
49
|
+
if (env in config) {
|
|
50
|
+
Object.assign(config, { ...config[env] });
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
52
|
+
delete config[env];
|
|
53
|
+
} else if (env) {
|
|
54
|
+
throw new Error(`Missing configuration for environment "${env}"!`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Support legacy config, remote_path instead of root
|
|
58
|
+
if ('remote_path' in config) {
|
|
59
|
+
config.root = config.remote_path;
|
|
43
60
|
|
|
44
|
-
|
|
61
|
+
delete config.remote_path;
|
|
45
62
|
}
|
|
46
63
|
|
|
47
|
-
|
|
64
|
+
return config;
|
|
48
65
|
}
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Reset the config, useful when unit testing.
|
|
69
|
+
*
|
|
70
|
+
* @returns {Object}
|
|
71
|
+
*/
|
|
72
|
+
export function reset() {
|
|
73
|
+
const old = { ...config };
|
|
51
74
|
|
|
75
|
+
Object.entries(config).forEach(([key]) => {
|
|
76
|
+
delete config[key];
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return old;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default config;
|
package/src/date.js
CHANGED
|
@@ -29,32 +29,32 @@ export function relative(date) {
|
|
|
29
29
|
value = 'just';
|
|
30
30
|
unit = 'now';
|
|
31
31
|
agoOrFromNow = '';
|
|
32
|
-
// Less than a minute
|
|
33
32
|
} else if (seconds < 60) {
|
|
33
|
+
// Less than a minute
|
|
34
34
|
value = Math.floor(seconds);
|
|
35
35
|
unit = plural('second', value);
|
|
36
|
-
// Less than an hour
|
|
37
36
|
} else if (seconds < 60 * 60) {
|
|
37
|
+
// Less than an hour
|
|
38
38
|
value = Math.floor(seconds / 60);
|
|
39
39
|
unit = plural('minute', value);
|
|
40
|
-
// Less than a day
|
|
41
40
|
} else if (seconds < 60 * 60 * 24) {
|
|
41
|
+
// Less than a day
|
|
42
42
|
value = Math.floor(seconds / 60 / 60);
|
|
43
43
|
unit = plural('hour', value);
|
|
44
|
-
// Less than a week
|
|
45
44
|
} else if (seconds < 60 * 60 * 24 * 7) {
|
|
45
|
+
// Less than a week
|
|
46
46
|
value = Math.floor(seconds / 60 / 60 / 24);
|
|
47
47
|
unit = plural('day', value);
|
|
48
|
-
// Less than a month
|
|
49
48
|
} else if (seconds < 60 * 60 * 24 * 30) {
|
|
49
|
+
// Less than a month
|
|
50
50
|
value = Math.floor(seconds / 60 / 60 / 24 / 7);
|
|
51
51
|
unit = plural('week', value);
|
|
52
|
-
// Less than a year
|
|
53
52
|
} else if (seconds < 60 * 60 * 24 * 365) {
|
|
53
|
+
// Less than a year
|
|
54
54
|
value = Math.floor(seconds / 60 / 60 / 24 / 30);
|
|
55
55
|
unit = plural('month', value);
|
|
56
56
|
} else {
|
|
57
|
-
return `${months[datetime.getMonth()]} ${datetime.
|
|
57
|
+
return `${months[datetime.getMonth()]} ${datetime.getDate()}, ${datetime.getFullYear()} @ ${datetime.getHours() % 12}:${`0${datetime.getMinutes()}`.slice(-2)} ${datetime.getHours() >= 12 ? 'pm' : 'am'}`;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
return `${value} ${unit} ${agoOrFromNow}`.trim();
|
package/src/diff.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { statSync } from 'node:fs';
|
|
3
3
|
import * as ansi from './ansi.js';
|
|
4
|
+
import * as cli from './cli.js';
|
|
4
5
|
import config from './config.js';
|
|
5
6
|
import debug from './debug.js';
|
|
6
7
|
|
|
@@ -15,17 +16,17 @@ export default async function diff(dryRunOutput, options) {
|
|
|
15
16
|
const changes = dryRunOutput
|
|
16
17
|
.split(/\r?\n/)
|
|
17
18
|
.filter(Boolean)
|
|
18
|
-
.map(line => {
|
|
19
|
+
.map((line) => {
|
|
19
20
|
// Split line into action and file, preserving whitespace for filenames
|
|
20
21
|
const [action, ...parts] = line.split(/\s+/);
|
|
21
22
|
|
|
22
23
|
let file, target;
|
|
23
24
|
|
|
24
|
-
// Links also have a target
|
|
25
|
-
if (action
|
|
25
|
+
// Links also have a target, cL+++++++++ or cLc.T......
|
|
26
|
+
if (action.match(/^cL.+/)) {
|
|
26
27
|
const index = parts.indexOf('->');
|
|
27
28
|
|
|
28
|
-
debug({ parts })
|
|
29
|
+
debug({ parts });
|
|
29
30
|
|
|
30
31
|
file = parts.slice(0, index).join(' ');
|
|
31
32
|
target = parts.slice(index + 1).join(' ');
|
|
@@ -35,65 +36,75 @@ export default async function diff(dryRunOutput, options) {
|
|
|
35
36
|
|
|
36
37
|
return [action, file, target];
|
|
37
38
|
})
|
|
38
|
-
.filter(([
|
|
39
|
+
.filter(([, file]) => file.slice(-1) !== '/');
|
|
39
40
|
|
|
40
|
-
debug({ changes })
|
|
41
|
+
debug({ changes });
|
|
41
42
|
|
|
42
43
|
let proceed = true;
|
|
43
44
|
|
|
44
45
|
if (changes.length >= 50 && options.interactive) {
|
|
45
|
-
proceed = await cli.agree(
|
|
46
|
+
proceed = await cli.agree(
|
|
47
|
+
ansi.yellow(`${changes.length} file(s) will be compared. Continue? `)
|
|
48
|
+
);
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
if (!proceed) {
|
|
49
52
|
return Promise.reject('Process canceled by user');
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
const files = changes.map(([
|
|
55
|
+
const files = changes.map(([, file]) => file);
|
|
53
56
|
|
|
54
|
-
const diffs = changes
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
];
|
|
57
|
+
const diffs = changes
|
|
58
|
+
.sort((a, b) => a[1].localeCompare(b[1]))
|
|
59
|
+
.map((change) => {
|
|
60
|
+
const [action, file] = change;
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
// Escape double quotes in file
|
|
63
|
+
const fileEscaped = file.replace(/"/g, '\\"');
|
|
61
64
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
const header = [
|
|
66
|
+
file.match(/"/)
|
|
67
|
+
? `diff --git "a/${fileEscaped}" "b/${fileEscaped}"`
|
|
68
|
+
: `diff --git a/${file} b/${file}`,
|
|
69
|
+
];
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
} else if (action === '<f+++++++++') {
|
|
68
|
-
const { mode } = statSync(file);
|
|
71
|
+
let diff = '';
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
if (action === '*deleting') {
|
|
74
|
+
header.push('deleted file mode 100644');
|
|
75
|
+
header.push('index 0000000..0000000');
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
diff = `diff -Naurp --label="a/${fileEscaped}" --label=/dev/null "${fileEscaped}" /dev/null`;
|
|
78
|
+
} else if (action.match(/^<f\++/)) {
|
|
79
|
+
const { mode } = statSync(file);
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
header.push(`new file mode ${mode.toString(8)}`);
|
|
82
|
+
header.push('index 0000000..0000000');
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
diff = `diff -Naurp --label=/dev/null --label="b/${fileEscaped}" /dev/null <(tar xzOf $tarball "${fileEscaped}" || cat /dev/null)`;
|
|
85
|
+
} else if (action.match(/^cL\++/)) {
|
|
86
|
+
const target = change[2];
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
header.push('new file mode 120000');
|
|
89
|
+
header.push('index 0000000..0000000');
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
diff = `diff -Naurp --label=/dev/null --label="b/${fileEscaped}" /dev/null <(printf %s "${target}")`;
|
|
92
|
+
} else if (action.match(/^cL.+/)) {
|
|
93
|
+
const target = change[2];
|
|
89
94
|
|
|
90
|
-
|
|
95
|
+
header.push('index 0000000..0000000');
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" <(printf %s "$(readlink "${fileEscaped}")") <(printf %s "${target}")`;
|
|
98
|
+
} else {
|
|
99
|
+
const { mode } = statSync(file);
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
header.push(`index 0000000..0000000 ${mode.toString(8)}`);
|
|
102
|
+
|
|
103
|
+
diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" "${fileEscaped}" <(tar xzOf $tarball "${fileEscaped}" || cat /dev/null)`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return `printf "%s\\n" '${header.join('\n')}'; ${diff}`;
|
|
107
|
+
});
|
|
97
108
|
|
|
98
109
|
if (diffs.length > 0) {
|
|
99
110
|
debug({ diffs, files });
|
|
@@ -103,40 +114,53 @@ export default async function diff(dryRunOutput, options) {
|
|
|
103
114
|
const ssh = spawn('ssh', [
|
|
104
115
|
config.server,
|
|
105
116
|
'--',
|
|
106
|
-
'tarball=$(mktemp /tmp/frak-diff-tarball.
|
|
117
|
+
'tarball=$(mktemp /tmp/frak-diff-tarball.XXXXXX);',
|
|
107
118
|
'cat > $tarball;',
|
|
108
119
|
`cd ${config.root};`,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
])
|
|
120
|
+
diffs.join('; '),
|
|
121
|
+
';',
|
|
122
|
+
'rm $tarball',
|
|
123
|
+
]);
|
|
113
124
|
tar.stdout.pipe(ssh.stdin);
|
|
114
125
|
|
|
115
126
|
let less;
|
|
116
127
|
|
|
117
128
|
if (options.interactive) {
|
|
118
|
-
less = spawn('less', ['-
|
|
129
|
+
less = spawn('less', ['-R'], { stdio: ['pipe', process.stdout, process.stderr] });
|
|
119
130
|
}
|
|
120
131
|
|
|
121
132
|
if (options.interactive && options.colorize) {
|
|
122
133
|
const colorize = spawn('sed', [
|
|
123
134
|
// headers
|
|
124
|
-
'-e',
|
|
125
|
-
|
|
126
|
-
'-e',
|
|
127
|
-
|
|
128
|
-
'-e',
|
|
129
|
-
|
|
135
|
+
'-e',
|
|
136
|
+
`s/^\\(diff --git .*\\)/${ansi.bold('\\1')}/`,
|
|
137
|
+
'-e',
|
|
138
|
+
`s/^\\(index .*\\)/${ansi.bold('\\1')}/`,
|
|
139
|
+
'-e',
|
|
140
|
+
`s/^\\(new file mode .*\\)/${ansi.bold('\\1')}/`,
|
|
141
|
+
'-e',
|
|
142
|
+
`s/^\\(deleted file mode .*\\)/${ansi.bold('\\1')}/`,
|
|
143
|
+
'-e',
|
|
144
|
+
`s/^\\(--- .*\\)/${ansi.bold('\\1')}/`,
|
|
145
|
+
'-e',
|
|
146
|
+
`s/^\\(+++ .*\\)/${ansi.bold('\\1')}/`,
|
|
130
147
|
// additions
|
|
131
|
-
'-e',
|
|
132
|
-
|
|
148
|
+
'-e',
|
|
149
|
+
`s/^\\(+[^+].*\\)/${ansi.green('\\1')}/`,
|
|
150
|
+
'-e',
|
|
151
|
+
`s/^\\(+\\)$/${ansi.green('\\1')}/`,
|
|
133
152
|
// deletions
|
|
134
|
-
'-e',
|
|
135
|
-
|
|
153
|
+
'-e',
|
|
154
|
+
`s/^\\(-[^-].*\\)/${ansi.red('\\1')}/`,
|
|
155
|
+
'-e',
|
|
156
|
+
`s/^\\(-\\)$/${ansi.red('\\1')}/`,
|
|
136
157
|
// markers
|
|
137
|
-
'-e',
|
|
138
|
-
|
|
139
|
-
'-e',
|
|
158
|
+
'-e',
|
|
159
|
+
`s/^\\(@@ -[0-9][0-9]*,[0-9][0-9]* +[0-9][0-9]*,[0-9][0-9]* .*@@\\)/${ansi.blue('\\1')}/`,
|
|
160
|
+
'-e',
|
|
161
|
+
`s/^\\(@@ -[0-9][0-9]*,[0-9][0-9]* +[0-9][0-9]* .*@@\\)/${ansi.blue('\\1')}/`,
|
|
162
|
+
'-e',
|
|
163
|
+
`s/^\\(@@ -[0-9][0-9]* +[0-9][0-9]*,[0-9][0-9]* .*@@\\)/${ansi.blue('\\1')}/`,
|
|
140
164
|
]);
|
|
141
165
|
ssh.stdout.pipe(colorize.stdin);
|
|
142
166
|
colorize.stdout.pipe(less.stdin);
|
|
@@ -150,24 +174,30 @@ export default async function diff(dryRunOutput, options) {
|
|
|
150
174
|
if (code === 0) {
|
|
151
175
|
resolve();
|
|
152
176
|
} else {
|
|
177
|
+
/* node :coverage disable */
|
|
153
178
|
reject(`exited with code ${code}`);
|
|
179
|
+
/* node:coverage enable */
|
|
154
180
|
}
|
|
155
181
|
});
|
|
156
182
|
});
|
|
157
183
|
} else {
|
|
158
|
-
|
|
184
|
+
/* node:coverage disable */
|
|
185
|
+
return await new Promise((resolve) => {
|
|
159
186
|
let diff = '';
|
|
160
187
|
|
|
161
|
-
ssh.stdout.on('data', (data) => diff += data);
|
|
188
|
+
ssh.stdout.on('data', (data) => (diff += data));
|
|
162
189
|
|
|
163
190
|
ssh.stderr.pipe(process.stderr);
|
|
164
191
|
|
|
165
|
-
ssh.on('exit', (
|
|
192
|
+
ssh.on('exit', () => {
|
|
166
193
|
resolve(diff);
|
|
167
194
|
});
|
|
168
195
|
});
|
|
196
|
+
/* node:coverage enable */
|
|
169
197
|
}
|
|
170
198
|
} else if (options.interactive) {
|
|
171
199
|
console.log(ansi.yellow('No changes detected.'));
|
|
172
200
|
}
|
|
201
|
+
|
|
202
|
+
return '';
|
|
173
203
|
}
|
package/src/humanize.js
CHANGED
|
@@ -9,5 +9,5 @@ export default function humanize(changes) {
|
|
|
9
9
|
.replace(/<fc..\.\.\.\.\.\./g, ansi.yellow('Update '))
|
|
10
10
|
.replace(/>fc..\.\.\.\.\.\./g, ansi.yellow('Update '))
|
|
11
11
|
.replace(/cLc\.T\.\.\.\.\.\./g, ansi.yellow('Update '))
|
|
12
|
-
.replace(/\*deleting
|
|
12
|
+
.replace(/\*deleting {2}/g, ansi.red('Delete '))
|
|
13
13
|
}
|
package/src/rsync.js
CHANGED
|
@@ -19,6 +19,7 @@ export function filters() {
|
|
|
19
19
|
'.git*',
|
|
20
20
|
'.svn*',
|
|
21
21
|
'.tags*',
|
|
22
|
+
'frak.config.js',
|
|
22
23
|
'Capfile',
|
|
23
24
|
'Gemfile*',
|
|
24
25
|
'Vagrantfile*',
|
|
@@ -31,22 +32,24 @@ export function filters() {
|
|
|
31
32
|
|
|
32
33
|
// merge path
|
|
33
34
|
if (options.path) {
|
|
34
|
-
const paths = options.path
|
|
35
|
-
.
|
|
36
|
-
.map(p => p
|
|
35
|
+
const paths = options.path
|
|
36
|
+
.split(/((?<!\\) )+/)
|
|
37
|
+
.map((p) => p.replace(/\\ /g, ' '))
|
|
38
|
+
.map((p) => path.resolve(p))
|
|
39
|
+
.map((p) => p) // TODO glob // paths.map! { |p| p[/[*?]/] ? Dir.glob(p) : p }
|
|
37
40
|
.flat(Infinity)
|
|
38
41
|
// make paths relative to .frak file
|
|
39
|
-
.map(p => p.replace(path.dirname(locateConfig()), ''));
|
|
42
|
+
.map((p) => p.replace(path.dirname(locateConfig()), ''));
|
|
40
43
|
|
|
41
44
|
// include all parent directories to fix recursive rsync from excluding all
|
|
42
|
-
paths.forEach(p => {
|
|
45
|
+
paths.forEach((p) => {
|
|
43
46
|
includes.push(p);
|
|
44
47
|
|
|
45
48
|
while (path.dirname(p) !== '/') {
|
|
46
49
|
excludes.push(`${path.dirname(p)}/*`);
|
|
47
50
|
|
|
48
51
|
p = path.dirname(p);
|
|
49
|
-
includes.push(p)
|
|
52
|
+
includes.push(p);
|
|
50
53
|
}
|
|
51
54
|
});
|
|
52
55
|
|
|
@@ -58,8 +61,8 @@ export function filters() {
|
|
|
58
61
|
filters.push('.e .frakignore');
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
includes.forEach(include => filters.push(`+ ${include}`));
|
|
62
|
-
excludes.forEach(include => filters.push(`- ${include}`));
|
|
64
|
+
includes.forEach((include) => filters.push(`+ ${include}`));
|
|
65
|
+
excludes.forEach((include) => filters.push(`- ${include}`));
|
|
63
66
|
|
|
64
67
|
return filters;
|
|
65
68
|
}
|
|
@@ -88,35 +91,45 @@ export function exec(...options) {
|
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
let backup = [];
|
|
91
|
-
let backupName =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
let backupName =
|
|
95
|
+
'name' in commandOptions
|
|
96
|
+
? `${commandOptions.name}_${new Date().toISOString()}`
|
|
97
|
+
: new Date().toISOString();
|
|
94
98
|
|
|
95
99
|
// Figure out backup options
|
|
96
|
-
backup = [
|
|
97
|
-
'--exclude=.backups/',
|
|
98
|
-
];
|
|
100
|
+
backup = ['--exclude=.backups/'];
|
|
99
101
|
|
|
100
102
|
// Keep the same backup path for all rsync commands
|
|
101
103
|
if (backupPath === null) {
|
|
102
|
-
backupPath = `.backups/${backupName}
|
|
104
|
+
backupPath = `.backups/${backupName}`;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// This should only happen on push
|
|
106
108
|
if (command === 'push') {
|
|
107
|
-
backup = [...backup, '--backup', `--backup-dir
|
|
109
|
+
backup = [...backup, '--backup', `--backup-dir=${backupPath}`];
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
const args = [
|
|
111
113
|
'rsync',
|
|
112
114
|
[
|
|
113
|
-
'--archive',
|
|
114
|
-
'--
|
|
115
|
+
'--archive',
|
|
116
|
+
'--no-group',
|
|
117
|
+
'--no-owner',
|
|
118
|
+
'--no-perms',
|
|
119
|
+
'--no-times',
|
|
120
|
+
'--human-readable',
|
|
121
|
+
'--compress',
|
|
122
|
+
'--checksum',
|
|
123
|
+
'--itemize-changes',
|
|
124
|
+
'--recursive',
|
|
125
|
+
'--delete',
|
|
126
|
+
process.env.DEBUG ? '--rsh=ssh' : '--rsh=ssh -q',
|
|
115
127
|
`--rsync-path=${config['rsync_path'] ? config['rsync_path'] : 'rsync'}`,
|
|
116
128
|
'--filter=. -',
|
|
117
129
|
...backup,
|
|
118
130
|
...options,
|
|
119
|
-
source,
|
|
131
|
+
source,
|
|
132
|
+
destination,
|
|
120
133
|
],
|
|
121
134
|
];
|
|
122
135
|
|
|
@@ -124,21 +137,27 @@ export function exec(...options) {
|
|
|
124
137
|
debug(args.flat().join(' '));
|
|
125
138
|
debug('filters:', filters());
|
|
126
139
|
|
|
127
|
-
let output = ''
|
|
140
|
+
let output = '',
|
|
141
|
+
stderr = '';
|
|
128
142
|
|
|
129
143
|
const rsync = spawn.apply(null, args);
|
|
130
144
|
|
|
131
|
-
const echo = spawn('echo', [
|
|
145
|
+
const echo = spawn('echo', [filters().join('\n')]);
|
|
132
146
|
|
|
133
147
|
echo.stdout.pipe(rsync.stdin);
|
|
134
148
|
|
|
135
149
|
rsync.stdout.on('data', (data) => {
|
|
136
150
|
output += `${data}`;
|
|
137
151
|
});
|
|
152
|
+
rsync.stderr.on('data', (data) => {
|
|
153
|
+
stderr += `${data}`;
|
|
154
|
+
});
|
|
138
155
|
|
|
156
|
+
/* node:coverage disable */
|
|
139
157
|
rsync.stderr.on('data', (data) => {
|
|
140
158
|
process.stderr.write(`${data}`); // Print any errors from the child process
|
|
141
159
|
});
|
|
160
|
+
/* node:coverage enable */
|
|
142
161
|
|
|
143
162
|
return new Promise((resolve, reject) => {
|
|
144
163
|
rsync.on('exit', (code) => {
|
|
@@ -148,7 +167,9 @@ export function exec(...options) {
|
|
|
148
167
|
backupPath,
|
|
149
168
|
});
|
|
150
169
|
} else {
|
|
151
|
-
|
|
170
|
+
/* node:coverage disable */
|
|
171
|
+
reject(`rsync exited with code ${code}: ${stderr}`);
|
|
172
|
+
/* node:coverage enable */
|
|
152
173
|
}
|
|
153
174
|
});
|
|
154
175
|
});
|
package/src/ssh.js
CHANGED
|
@@ -6,7 +6,7 @@ export function exec(command, options = {}) {
|
|
|
6
6
|
|
|
7
7
|
const cmd = `cd ${options.root || '.'}; sh`;
|
|
8
8
|
|
|
9
|
-
const ssh = spawn('ssh', [
|
|
9
|
+
const ssh = spawn('ssh', [options.server, '--', cmd]);
|
|
10
10
|
|
|
11
11
|
// const echo = spawn('echo', [ command ]);
|
|
12
12
|
|
|
@@ -32,7 +32,9 @@ export function exec(command, options = {}) {
|
|
|
32
32
|
if (code === 0) {
|
|
33
33
|
resolve();
|
|
34
34
|
} else {
|
|
35
|
+
/* node:coverage disable */
|
|
35
36
|
reject(`exited with code ${code}`);
|
|
37
|
+
/* node:coverage enable */
|
|
36
38
|
}
|
|
37
39
|
});
|
|
38
40
|
});
|
package/.frakignore
DELETED
|
File without changes
|
package/eslint.config.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default [{}]
|
package/frak.config.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|