@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 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
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-upload-icon lucide-cloud-upload"><path d="M12 13v8"/><path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"/><path d="m8 17 4-4 4 4"/></svg>
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
- Quick deployment tool using Rsync and SSH.
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 an agentless deployment tool for web apps. 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 post-deploy hooks.
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
- > “Frak it, ship it.”
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 `frak` is via the `npx` command.
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
- Your Name - [@strube](https://twitter.com/strube) - contact@frakjs.com
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
- [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
211
- [linkedin-url]: https://linkedin.com/in/linkedin_username
212
- [product-screenshot]: images/screenshot.png
213
- [prerequisites-screenshot]: images/node-npm-version.png
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 { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs';
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.log(ansi.red(e.message));
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
- const { output } = await rsync.exec('--dry-run');
90
+ try {
91
+ const { output } = await rsync.exec('--dry-run');
95
92
 
96
- await diff(output, options);
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, '\n');
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
- try {
121
- patch = await diff(output, { interactive: false });
122
- } catch (e) {
123
- console.error(e);
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
- process.exit(1);
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
- const hash = sha256(patch).slice(0, 7);
136
- const copy = spawn('ssh', [config.server, '--', `cat > '${config.root || '.'}/${backupPath}/${hash}.patch'`]);
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
- copy.stdout.on('data', (data) => debug('' + data));
139
- copy.stderr.on('data', (data) => debug('' + data));
140
- copy.stdin.write(patch);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frakjs/frak",
3
- "version": "0.1.1",
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
- "test": "echo \"Error: no test specified\" && exit 1"
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.includes('=')) {
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.write(string);
32
+ export async function agree(string, { stdin = null, stdout = null } = {}) {
33
+ stdout = stdout ?? process.stdout;
34
+ stdin = stdin ?? process.stdin;
30
35
 
31
- const answer = await new Promise((resolve, reject) => {
32
- process.stdin.on('data', data => resolve(data));
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
- if (env in config) {
37
- return Object.assign(config, {...config[env]});
38
- }
49
+ if (env in config) {
50
+ Object.assign(config, {...config[env]});
39
51
 
40
- if (env) {
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
- throw new Error('Config file not found! Try running frak init.');
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.getDay()}, ${datetime.getFullYear()} @ ${datetime.getHours() % 12}:${`0${datetime.getMinutes()}`.slice(-2)} ${datetime.getHours() >= 12 ? 'pm' : 'am'}`;
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 === 'cL+++++++++' || action === 'cLc.T......') {
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(([action, file]) => file.slice(-1) !== '/');
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(([action, file]) => file);
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 = ['diff', '-Naurp', `--label=a/${file}`, `--label=/dev/null`, file, '/dev/null'];
67
- } else if (action === '<f+++++++++') {
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 = ['diff', '-Naurp', `--label=/dev/null`, `--label=b/${file}`, '/dev/null', `<(tar xzOf $tarball --warning=no-unknown-keyword ${file} || cat /dev/null)`];
74
- } else if (action === 'cLc.T......') {
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 = ['diff', '-Naurp', `--label=a/${file}`, `--label=b/${file}`, `<(printf %s $(readlink ${file}))`, `<(printf %s ${target})`];
80
- } else if (action === 'cL+++++++++') {
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 = ['diff', '-Naurp', `--label=/dev/null`, `--label=b/${file}`, '/dev/null', `<(printf %s ${target})`];
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 = ['diff', '-Naurp', `--label=a/${file}`, `--label=b/${file}`, file, `<(tar xzOf $tarball --warning=no-unknown-keyword ${file} || cat /dev/null)`];
97
+ diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" "${fileEscaped}" <(tar xzOf $tarball "${fileEscaped}" || cat /dev/null)`;
93
98
  }
94
99
 
95
- return [`printf "%s\\n" "${header.join('\n')}"`, ';', ...diff, ';'];
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.XXXXXXXX);',
111
+ 'tarball=$(mktemp /tmp/frak-diff-tarball.XXXXXX);',
107
112
  'cat > $tarball;',
108
113
  `cd ${config.root};`,
109
- ...diffs.reduce((commands, diff) => commands.concat(...diff), []),
110
- // `${diffs.join(';')};`,
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', ['-r'], { stdio: ['pipe', 'inherit', 'inherit'] });
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, reject) => {
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', (code) => {
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 /g, ansi.red('Delete '))
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
@@ -31,9 +31,11 @@ export function exec(command, options = {}) {
31
31
  ssh.on('exit', (code) => {
32
32
  if (code === 0) {
33
33
  resolve();
34
+ /* node:coverage disable */
34
35
  } else {
35
36
  reject(`exited with code ${code}`);
36
37
  }
38
+ /* node:coverage enable */
37
39
  });
38
40
  });
39
41
 
package/.frakignore DELETED
File without changes
package/eslint.config.js DELETED
@@ -1 +0,0 @@
1
- export default [{}]
package/frak.config.js DELETED
@@ -1,10 +0,0 @@
1
- export default {
2
- server: 'franklinstrube.com',
3
- root: '/home/ash/frak',
4
- after: 'touch deployed; pwd',
5
- method: 'rsync',
6
-
7
- production: {
8
- root: '/home/ash/frak-production',
9
- },
10
- };