@frakjs/frak 0.1.1 → 0.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/README.md +11 -15
- package/bin/frak.js +35 -20
- 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 - Light - 256.png +0 -0
- package/package.json +12 -3
- package/src/ansi.js +1 -1
- package/src/cli.js +12 -6
- package/src/config.js +44 -8
- package/src/date.js +1 -1
- package/src/debug.js +1 -1
- package/src/diff.js +34 -24
- package/src/humanize.js +1 -1
- package/src/rsync.js +7 -1
- package/src/ssh.js +2 -0
- 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/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,8 +14,6 @@ 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
|
*
|
|
@@ -26,7 +23,7 @@ async function tryLoadConfig(env) {
|
|
|
26
23
|
try {
|
|
27
24
|
await loadConfig(env);
|
|
28
25
|
} catch (e) {
|
|
29
|
-
console.
|
|
26
|
+
console.error(ansi.red(e.message));
|
|
30
27
|
|
|
31
28
|
process.exit(1);
|
|
32
29
|
}
|
|
@@ -58,7 +55,6 @@ async function main(args) {
|
|
|
58
55
|
|
|
59
56
|
debug('parsed:', { command, options });
|
|
60
57
|
|
|
61
|
-
|
|
62
58
|
// Initialize Frak in the current directory
|
|
63
59
|
if (command === 'init') {
|
|
64
60
|
const configPath = path.join(process.cwd(), 'frak.config.js');
|
|
@@ -91,9 +87,17 @@ async function main(args) {
|
|
|
91
87
|
} else if (command === 'diff') {
|
|
92
88
|
await tryLoadConfig(options.env);
|
|
93
89
|
|
|
94
|
-
|
|
90
|
+
try {
|
|
91
|
+
const { output } = await rsync.exec('--dry-run');
|
|
95
92
|
|
|
96
|
-
|
|
93
|
+
debug({ output });
|
|
94
|
+
|
|
95
|
+
await diff(output, options);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
debug(e);
|
|
98
|
+
|
|
99
|
+
console.error(e.message);
|
|
100
|
+
}
|
|
97
101
|
|
|
98
102
|
// Push or pull changes to remote server
|
|
99
103
|
} else if (command === 'push' || command === 'pull') {
|
|
@@ -102,10 +106,13 @@ async function main(args) {
|
|
|
102
106
|
// Execute dry-run
|
|
103
107
|
const { output } = await rsync.exec('--dry-run');
|
|
104
108
|
|
|
109
|
+
debug({ output });
|
|
110
|
+
|
|
105
111
|
console.log(humanize(output));
|
|
106
112
|
|
|
107
113
|
if (command === 'push' && config.after) {
|
|
108
|
-
console.log(ansi.blue('Run command:'), config.after
|
|
114
|
+
console.log(ansi.blue('Run command:'), config.after);
|
|
115
|
+
console.log('');
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
const proceed = await cli.agree(ansi.yellow('The above actions will be taken. Continue? (This cannot be undone): '));
|
|
@@ -115,14 +122,18 @@ async function main(args) {
|
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
// Prepare patch file
|
|
118
|
-
let patch;
|
|
125
|
+
let patch = '';
|
|
119
126
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
if (command === 'push') {
|
|
128
|
+
try {
|
|
129
|
+
patch = await diff(output, { interactive: false });
|
|
130
|
+
/* node:coverage disable */
|
|
131
|
+
} catch (e) {
|
|
132
|
+
console.error(e);
|
|
124
133
|
|
|
125
|
-
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
/* node:coverage enable */
|
|
126
137
|
}
|
|
127
138
|
|
|
128
139
|
// Execute actual rsync command
|
|
@@ -132,12 +143,14 @@ 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', [config.server, '--', `cat > '${config.root || '.'}/${backupPath}/${hash}.patch'`]);
|
|
137
149
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
copy.stdout.on('data', (data) => debug('' + data));
|
|
151
|
+
copy.stderr.on('data', (data) => debug('' + data));
|
|
152
|
+
copy.stdin.write(patch);
|
|
153
|
+
}
|
|
141
154
|
|
|
142
155
|
// Execute "after" command
|
|
143
156
|
if (command === 'push' && config.after) {
|
|
@@ -147,9 +160,11 @@ async function main(args) {
|
|
|
147
160
|
|
|
148
161
|
try {
|
|
149
162
|
await ssh.exec(config.after, { server: config.server, root: config.root });
|
|
163
|
+
/* node:coverage disable */
|
|
150
164
|
} catch (e) {
|
|
151
165
|
debug(e);
|
|
152
166
|
}
|
|
167
|
+
/* node:coverage enable */
|
|
153
168
|
}
|
|
154
169
|
|
|
155
170
|
// TODO Webhook
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frakjs/frak",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A commandline deployment tool for syncing files to remote servers.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"
|
|
11
|
+
"eslint": "eslint",
|
|
12
|
+
"test": "node --test --experimental-test-coverage"
|
|
12
13
|
},
|
|
13
14
|
"repository": {
|
|
14
15
|
"type": "git",
|
|
@@ -24,5 +25,13 @@
|
|
|
24
25
|
"bugs": {
|
|
25
26
|
"url": "https://github.com/frakjs/frak/issues"
|
|
26
27
|
},
|
|
27
|
-
"homepage": "https://github.com/frakjs/frak#readme"
|
|
28
|
+
"homepage": "https://github.com/frakjs/frak#readme",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@eslint/js": "^9.33.0",
|
|
31
|
+
"eslint": "^9.33.0",
|
|
32
|
+
"globals": "^16.3.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"yaml": "^2.8.1"
|
|
36
|
+
}
|
|
28
37
|
}
|
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());
|
|
@@ -67,7 +74,6 @@ export class Table {
|
|
|
67
74
|
const width = ansi.strip(cell).length
|
|
68
75
|
const padding = Math.max(0, this.columns[i].width - width - 2);
|
|
69
76
|
const paddingString = (new Array(padding + 1)).join(' ');
|
|
70
|
-
const borderString = (new Array(this.columns[i].width + 1).join('-'));
|
|
71
77
|
|
|
72
78
|
return `${row || ''}${ansi.gray('|')} ${cell}${paddingString} ${i === r.length - 1 ? ansi.gray('|') : ''}`
|
|
73
79
|
}, '');
|
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 { parse } 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,21 +36,51 @@ 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, 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
|
-
throw new Error(`Missing configuration for environment "${env}"!`);
|
|
42
|
-
}
|
|
52
|
+
delete config[env];
|
|
43
53
|
|
|
44
54
|
return config;
|
|
45
55
|
}
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
if (env) {
|
|
58
|
+
throw new Error(`Missing configuration for environment "${env}"!`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Support legacy config, remote_path instead of root
|
|
62
|
+
if ('remote_path' in config) {
|
|
63
|
+
config.root = config.remote_path;
|
|
64
|
+
|
|
65
|
+
delete config.remote_path;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return config;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Reset the config, useful when unit testing.
|
|
73
|
+
*
|
|
74
|
+
* @returns {Object}
|
|
75
|
+
*/
|
|
76
|
+
export function reset() {
|
|
77
|
+
const old = { ...config };
|
|
78
|
+
|
|
79
|
+
Object.entries(config).forEach(([key]) => {
|
|
80
|
+
delete config[key];
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return old;
|
|
48
84
|
}
|
|
49
85
|
|
|
50
86
|
export default config;
|
package/src/date.js
CHANGED
|
@@ -54,7 +54,7 @@ export function relative(date) {
|
|
|
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/debug.js
CHANGED
|
@@ -5,6 +5,6 @@ import * as ansi from '../src/ansi.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export default function debug() {
|
|
7
7
|
if (process.env.DEBUG) {
|
|
8
|
-
console.log.apply(console, [ansi.bold(ansi.cyan('DEBUG:')), ...arguments]);
|
|
8
|
+
console.log.apply(console, [ ansi.bold(ansi.cyan('DEBUG:')), ...arguments ]);
|
|
9
9
|
}
|
|
10
10
|
}
|
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
|
|
|
@@ -21,8 +22,8 @@ export default async function diff(dryRunOutput, options) {
|
|
|
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
29
|
debug({ parts })
|
|
@@ -35,64 +36,68 @@ 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
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(ansi.yellow(`${changes.length} file(s) will be compared. Continue
|
|
46
|
+
proceed = await cli.agree(ansi.yellow(`${changes.length} file(s) will be compared. Continue? `))
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
if (!proceed) {
|
|
49
50
|
return Promise.reject('Process canceled by user');
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const files = changes.map(([
|
|
53
|
+
const files = changes.map(([, file]) => file);
|
|
53
54
|
|
|
54
55
|
const diffs = changes.sort((a, b) => a[1].localeCompare(b[1])).map(change => {
|
|
55
56
|
const [action, file] = change;
|
|
57
|
+
|
|
58
|
+
// Escape double quotes in file
|
|
59
|
+
const fileEscaped = file.replace(/"/g, '\\"');
|
|
60
|
+
|
|
56
61
|
const header = [
|
|
57
|
-
`diff --git a/${file} b/${file}`,
|
|
62
|
+
file.match(/"/) ? `diff --git "a/${fileEscaped}" "b/${fileEscaped}"` : `diff --git a/${file} b/${file}`,
|
|
58
63
|
];
|
|
59
64
|
|
|
60
|
-
let diff =
|
|
65
|
+
let diff = '';
|
|
61
66
|
|
|
62
67
|
if (action === '*deleting') {
|
|
63
68
|
header.push('deleted file mode 100644');
|
|
64
69
|
header.push('index 0000000..0000000');
|
|
65
70
|
|
|
66
|
-
diff =
|
|
67
|
-
} else if (action
|
|
71
|
+
diff = `diff -Naurp --label="a/${fileEscaped}" --label=/dev/null "${fileEscaped}" /dev/null`;
|
|
72
|
+
} else if (action.match(/^<f\++/)) {
|
|
68
73
|
const { mode } = statSync(file);
|
|
69
74
|
|
|
70
75
|
header.push(`new file mode ${mode.toString(8)}`);
|
|
71
76
|
header.push('index 0000000..0000000');
|
|
72
77
|
|
|
73
|
-
diff =
|
|
74
|
-
} else if (action
|
|
78
|
+
diff = `diff -Naurp --label=/dev/null --label="b/${fileEscaped}" /dev/null <(tar xzOf $tarball "${fileEscaped}" || cat /dev/null)`;
|
|
79
|
+
} else if (action.match(/^cL\++/)) {
|
|
75
80
|
const target = change[2];
|
|
76
81
|
|
|
82
|
+
header.push('new file mode 120000');
|
|
77
83
|
header.push('index 0000000..0000000');
|
|
78
84
|
|
|
79
|
-
diff =
|
|
80
|
-
} else if (action
|
|
85
|
+
diff = `diff -Naurp --label=/dev/null --label="b/${fileEscaped}" /dev/null <(printf %s "${target}")`;
|
|
86
|
+
} else if (action.match(/^cL.+/)) {
|
|
81
87
|
const target = change[2];
|
|
82
88
|
|
|
83
|
-
header.push('new file mode 120000');
|
|
84
89
|
header.push('index 0000000..0000000');
|
|
85
90
|
|
|
86
|
-
diff =
|
|
91
|
+
diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" <(printf %s "$(readlink "${fileEscaped}")") <(printf %s "${target}")`;
|
|
87
92
|
} else {
|
|
88
93
|
const { mode } = statSync(file);
|
|
89
94
|
|
|
90
95
|
header.push(`index 0000000..0000000 ${mode.toString(8)}`);
|
|
91
96
|
|
|
92
|
-
diff =
|
|
97
|
+
diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" "${fileEscaped}" <(tar xzOf $tarball "${fileEscaped}" || cat /dev/null)`;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
return
|
|
100
|
+
return `printf "%s\\n" '${header.join('\n')}'; ${diff}`;
|
|
96
101
|
});
|
|
97
102
|
|
|
98
103
|
if (diffs.length > 0) {
|
|
@@ -103,19 +108,18 @@ export default async function diff(dryRunOutput, options) {
|
|
|
103
108
|
const ssh = spawn('ssh', [
|
|
104
109
|
config.server,
|
|
105
110
|
'--',
|
|
106
|
-
'tarball=$(mktemp /tmp/frak-diff-tarball.
|
|
111
|
+
'tarball=$(mktemp /tmp/frak-diff-tarball.XXXXXX);',
|
|
107
112
|
'cat > $tarball;',
|
|
108
113
|
`cd ${config.root};`,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// 'rm $tarball',
|
|
114
|
+
diffs.join('; '), ';',
|
|
115
|
+
'rm $tarball',
|
|
112
116
|
])
|
|
113
117
|
tar.stdout.pipe(ssh.stdin);
|
|
114
118
|
|
|
115
119
|
let less;
|
|
116
120
|
|
|
117
121
|
if (options.interactive) {
|
|
118
|
-
less = spawn('less', ['-
|
|
122
|
+
less = spawn('less', ['-R'], { stdio: ['pipe', process.stdout, process.stderr] });
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
if (options.interactive && options.colorize) {
|
|
@@ -149,25 +153,31 @@ export default async function diff(dryRunOutput, options) {
|
|
|
149
153
|
less.on('exit', (code) => {
|
|
150
154
|
if (code === 0) {
|
|
151
155
|
resolve();
|
|
156
|
+
/* node:coverage disable */
|
|
152
157
|
} else {
|
|
153
158
|
reject(`exited with code ${code}`);
|
|
154
159
|
}
|
|
160
|
+
/* node:coverage enable */
|
|
155
161
|
});
|
|
156
162
|
});
|
|
163
|
+
/* node:coverage disable */
|
|
157
164
|
} else {
|
|
158
|
-
return await new Promise((resolve
|
|
165
|
+
return await new Promise((resolve) => {
|
|
159
166
|
let diff = '';
|
|
160
167
|
|
|
161
168
|
ssh.stdout.on('data', (data) => diff += data);
|
|
162
169
|
|
|
163
170
|
ssh.stderr.pipe(process.stderr);
|
|
164
171
|
|
|
165
|
-
ssh.on('exit', (
|
|
172
|
+
ssh.on('exit', () => {
|
|
166
173
|
resolve(diff);
|
|
167
174
|
});
|
|
168
175
|
});
|
|
169
176
|
}
|
|
177
|
+
/* node:coverage enable */
|
|
170
178
|
} else if (options.interactive) {
|
|
171
179
|
console.log(ansi.yellow('No changes detected.'));
|
|
172
180
|
}
|
|
181
|
+
|
|
182
|
+
return '';
|
|
173
183
|
}
|
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,7 +32,8 @@ export function filters() {
|
|
|
31
32
|
|
|
32
33
|
// merge path
|
|
33
34
|
if (options.path) {
|
|
34
|
-
const paths = options.path.split(/ +/)
|
|
35
|
+
const paths = options.path.split(/((?<!\\) )+/)
|
|
36
|
+
.map(p => p.replace(/\\ /g, ' '))
|
|
35
37
|
.map(p => path.resolve(p))
|
|
36
38
|
.map(p => p) // TODO glob // paths.map! { |p| p[/[*?]/] ? Dir.glob(p) : p }
|
|
37
39
|
.flat(Infinity)
|
|
@@ -136,9 +138,11 @@ export function exec(...options) {
|
|
|
136
138
|
output += `${data}`;
|
|
137
139
|
});
|
|
138
140
|
|
|
141
|
+
/* node:coverage disable */
|
|
139
142
|
rsync.stderr.on('data', (data) => {
|
|
140
143
|
process.stderr.write(`${data}`); // Print any errors from the child process
|
|
141
144
|
});
|
|
145
|
+
/* node:coverage enable */
|
|
142
146
|
|
|
143
147
|
return new Promise((resolve, reject) => {
|
|
144
148
|
rsync.on('exit', (code) => {
|
|
@@ -147,9 +151,11 @@ export function exec(...options) {
|
|
|
147
151
|
output,
|
|
148
152
|
backupPath,
|
|
149
153
|
});
|
|
154
|
+
/* node:coverage disable */
|
|
150
155
|
} else {
|
|
151
156
|
reject(`rsync exited with code ${code}`);
|
|
152
157
|
}
|
|
158
|
+
/* node:coverage enable */
|
|
153
159
|
});
|
|
154
160
|
});
|
|
155
161
|
}
|
package/src/ssh.js
CHANGED
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
|