@frakjs/frak 0.2.0 → 0.4.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 ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "tabWidth": 4,
3
+ "singleQuote": true,
4
+ "semi": true,
5
+ "trailingComma": "es5",
6
+ "printWidth": 100,
7
+ "bracketSpacing": true
8
+ }
package/bin/frak.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execSync, spawn } from 'node:child_process';
4
- import { writeFileSync, existsSync } from 'node:fs';
4
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
5
5
  import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
6
7
  import * as ansi from '../src/ansi.js';
7
8
  import * as cli from '../src/cli.js';
8
9
  import config, { load as loadConfig } from '../src/config.js';
@@ -14,21 +15,20 @@ import * as rsync from '../src/rsync.js';
14
15
  import * as ssh from '../src/ssh.js';
15
16
  import * as date from '../src/date.js';
16
17
 
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = path.dirname(__filename);
20
+
17
21
  /**
18
22
  * Try to load config. If it fails, print error message in red.
19
23
  *
20
24
  * @param {String} env
21
25
  */
22
26
  async function tryLoadConfig(env) {
23
- try {
24
- await loadConfig(env);
25
- } catch (e) {
27
+ return await loadConfig(env).catch((e) => {
26
28
  console.error(ansi.red(e.message));
27
29
 
28
30
  process.exit(1);
29
- }
30
-
31
- return;
31
+ });
32
32
  }
33
33
 
34
34
  /**
@@ -43,6 +43,19 @@ function usage() {
43
43
  console.log(' pull Download the remote file(s)');
44
44
  console.log(' push Deploy website via rsync');
45
45
  console.log(' backups:list List backups on the server');
46
+ console.log('');
47
+ console.log('Options:');
48
+ console.log(' --help Display the command usage');
49
+ console.log(' --version Show the current version');
50
+ }
51
+
52
+ /**
53
+ * Print the current version
54
+ */
55
+ function version() {
56
+ const { version } = JSON.parse(readFileSync(path.join(__dirname, '../package.json')));
57
+
58
+ console.log('Version:', version);
46
59
  }
47
60
 
48
61
  /**
@@ -55,20 +68,22 @@ async function main(args) {
55
68
 
56
69
  debug('parsed:', { command, options });
57
70
 
58
- // Initialize Frak in the current directory
59
71
  if (command === 'init') {
72
+ // Initialize Frak in the current directory
60
73
  const configPath = path.join(process.cwd(), 'frak.config.js');
61
74
 
62
75
  if (!existsSync(configPath)) {
63
- writeFileSync(configPath, `export default ${JSON.stringify({ server: 'example.com', root: '/var/www/html' }, null, 4)}`);
76
+ writeFileSync(
77
+ configPath,
78
+ `export default ${JSON.stringify({ server: 'example.com', root: '/var/www/html' }, null, 4)}`
79
+ );
64
80
  console.info(ansi.green('Configuration file created: frak.config.js'));
65
81
  } else {
66
82
  console.error(ansi.red('Frak is already initialized in this directory.'));
67
83
  }
68
-
69
- // Start a console session, or run the provided command on
70
- // the remote server.
71
84
  } else if (command === 'console') {
85
+ // Start a console session, or run the provided command on
86
+ // the remote server.
72
87
  await tryLoadConfig(options.env);
73
88
 
74
89
  if (!config.server) {
@@ -77,14 +92,16 @@ async function main(args) {
77
92
  }
78
93
 
79
94
  try {
80
- const cmd = options.command ?? '\\$SHELL -l -i';
95
+ const cmd = options.command || '\\$SHELL -l -i';
81
96
 
82
- execSync(`ssh -t ${config.server} -- cd ${config.root} \\&\\& ${cmd}`, { stdio: 'inherit' });
97
+ execSync(`ssh -t ${config.server} -- cd ${config.root} \\&\\& ${cmd}`, {
98
+ stdio: 'inherit',
99
+ });
83
100
  } catch (e) {
84
- debug(e)
101
+ debug(e);
85
102
  }
86
- // Show the differences between local files and remote server
87
103
  } else if (command === 'diff') {
104
+ // Show the differences between local files and remote server
88
105
  await tryLoadConfig(options.env);
89
106
 
90
107
  try {
@@ -98,9 +115,8 @@ async function main(args) {
98
115
 
99
116
  console.error(e.message);
100
117
  }
101
-
102
- // Push or pull changes to remote server
103
118
  } else if (command === 'push' || command === 'pull') {
119
+ // Push or pull changes to remote server
104
120
  await tryLoadConfig(options.env);
105
121
 
106
122
  // Execute dry-run
@@ -115,7 +131,9 @@ async function main(args) {
115
131
  console.log('');
116
132
  }
117
133
 
118
- const proceed = await cli.agree(ansi.yellow('The above actions will be taken. Continue? (This cannot be undone): '));
134
+ const proceed = await cli.agree(
135
+ ansi.yellow('The above actions will be taken. Continue? (This cannot be undone): ')
136
+ );
119
137
 
120
138
  if (!proceed) {
121
139
  process.exit(1);
@@ -125,14 +143,13 @@ async function main(args) {
125
143
  let patch = '';
126
144
 
127
145
  if (command === 'push') {
128
- try {
129
- patch = await diff(output, { interactive: false });
130
- /* node:coverage disable */
131
- } catch (e) {
132
- console.error(e);
133
-
134
- process.exit(1);
135
- }
146
+ patch = await diff(output, { interactive: false })
147
+ /* node:coverage disable */
148
+ .catch((e) => {
149
+ console.error(e);
150
+
151
+ process.exit(1);
152
+ });
136
153
  /* node:coverage enable */
137
154
  }
138
155
 
@@ -145,7 +162,11 @@ async function main(args) {
145
162
  // Place patch file in backup dir
146
163
  if (command === 'push') {
147
164
  const hash = sha256(patch).slice(0, 7);
148
- const copy = spawn('ssh', [config.server, '--', `cat > '${config.root || '.'}/${backupPath}/${hash}.patch'`]);
165
+ const copy = spawn('ssh', [
166
+ config.server,
167
+ '--',
168
+ `cat > '${config.root || '.'}/${backupPath}/${hash}.patch'`,
169
+ ]);
149
170
 
150
171
  copy.stdout.on('data', (data) => debug('' + data));
151
172
  copy.stderr.on('data', (data) => debug('' + data));
@@ -158,13 +179,12 @@ async function main(args) {
158
179
 
159
180
  debug({ command: config.after });
160
181
 
161
- try {
162
- await ssh.exec(config.after, { server: config.server, root: config.root });
163
- /* node:coverage disable */
164
- } catch (e) {
165
- debug(e);
166
- }
167
- /* node:coverage enable */
182
+ await ssh
183
+ .exec(config.after, {
184
+ server: config.server,
185
+ root: config.root,
186
+ })
187
+ .catch(debug);
168
188
  }
169
189
 
170
190
  // TODO Webhook
@@ -179,13 +199,16 @@ async function main(args) {
179
199
  const grep = {
180
200
  additions: `grep -e '^+' "$NAME"/*.patch | grep -v '^+++' | wc -l`,
181
201
  deletions: `grep -e '^-' "$NAME"/*.patch | grep -v '^---' | wc -l`,
182
- }
202
+ };
183
203
  const ls = `stat --format='%Y %n' .backups/* | sort -r`;
184
204
 
185
- const list = ssh.exec(`${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})'`, { quiet: true, server: config.server, root: config.root });
205
+ const list = ssh.exec(
206
+ `${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})'`,
207
+ { quiet: true, server: config.server, root: config.root }
208
+ );
186
209
  let backups = '';
187
210
 
188
- list.stdout.on('data', data => backups += data);
211
+ list.stdout.on('data', (data) => (backups += data));
189
212
 
190
213
  await list;
191
214
 
@@ -201,19 +224,26 @@ async function main(args) {
201
224
  {
202
225
  header: 'Changes',
203
226
  width: 30,
204
- }
227
+ },
205
228
  ]);
206
229
 
207
- backups.trim().split(/\n/).forEach(backup => {
208
- const [ timestamp, name, additions, deletions ] = backup.split(delimeter);
209
- table.row([
210
- name.replace(/^.backups\//, ''),
211
- date.relative(timestamp * 1000),
212
- ansi.green(`${additions} addition${additions == 1 ? '' : 's'}`) + ', ' + ansi.red(`${deletions} deletion${deletions == 1 ? '' : 's'}`),
213
- ]);
214
- });
230
+ backups
231
+ .trim()
232
+ .split(/\n/)
233
+ .forEach((backup) => {
234
+ const [timestamp, name, additions, deletions] = backup.split(delimeter);
235
+ table.row([
236
+ name.replace(/^.backups\//, ''),
237
+ date.relative(timestamp * 1000),
238
+ ansi.green(`${additions} addition${additions == 1 ? '' : 's'}`) +
239
+ ', ' +
240
+ ansi.red(`${deletions} deletion${deletions == 1 ? '' : 's'}`),
241
+ ]);
242
+ });
215
243
 
216
244
  table.print();
245
+ } else if (command === '--version' || command === 'version') {
246
+ version();
217
247
  } else {
218
248
  usage();
219
249
 
package/package.json CHANGED
@@ -1,37 +1,40 @@
1
1
  {
2
- "name": "@frakjs/frak",
3
- "version": "0.2.0",
4
- "description": "A commandline deployment tool for syncing files to remote servers.",
5
- "main": "index.js",
6
- "bin": {
7
- "frak": "bin/frak.js"
8
- },
9
- "type": "module",
10
- "scripts": {
11
- "eslint": "eslint",
12
- "test": "node --test --experimental-test-coverage"
13
- },
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://github.com/frakjs/frak.git"
17
- },
18
- "keywords": [
19
- "rsync",
20
- "deployment",
21
- "ssh"
22
- ],
23
- "author": "Frank Strube <contact@frakjs.com>",
24
- "license": "MIT",
25
- "bugs": {
26
- "url": "https://github.com/frakjs/frak/issues"
27
- },
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
- }
2
+ "name": "@frakjs/frak",
3
+ "version": "0.4.0",
4
+ "description": "A commandline deployment tool for syncing files to remote servers.",
5
+ "main": "index.js",
6
+ "engines": {
7
+ "node": ">=12"
8
+ },
9
+ "bin": {
10
+ "frak": "bin/frak.js"
11
+ },
12
+ "type": "module",
13
+ "scripts": {
14
+ "eslint": "eslint",
15
+ "test": "node --experimental-test-coverage tests/index.test.js"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/frakjs/frak.git"
20
+ },
21
+ "keywords": [
22
+ "rsync",
23
+ "deployment",
24
+ "ssh"
25
+ ],
26
+ "author": "Frank Strube <contact@frakjs.com>",
27
+ "license": "MIT",
28
+ "bugs": {
29
+ "url": "https://github.com/frakjs/frak/issues"
30
+ },
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
+ }
37
40
  }
package/src/cli.js CHANGED
@@ -30,13 +30,13 @@ export function parse(args) {
30
30
  }
31
31
 
32
32
  export async function agree(string, { stdin = null, stdout = null } = {}) {
33
- stdout = stdout ?? process.stdout;
34
- stdin = stdin ?? process.stdin;
33
+ stdout = stdout || process.stdout;
34
+ stdin = stdin || process.stdin;
35
35
 
36
36
  stdout.write(string);
37
37
 
38
38
  const answer = await new Promise((resolve) => {
39
- stdin.on('data', data => resolve(data));
39
+ stdin.on('data', (data) => resolve(data));
40
40
  });
41
41
 
42
42
  return ['y', 'yes'].includes(`${answer}`.trim().toLowerCase());
@@ -56,8 +56,8 @@ export class Table {
56
56
  const { border, header } = this.columns.reduce(({ header, border }, column, i) => {
57
57
  const width = ansi.strip(column.header).length;
58
58
  const padding = Math.max(0, column.width - width - 2);
59
- const paddingString = (new Array(padding + 1)).join(' ');
60
- const borderString = (new Array(column.width + 1).join('-'));
59
+ const paddingString = new Array(padding + 1).join(' ');
60
+ const borderString = new Array(column.width + 1).join('-');
61
61
 
62
62
  return {
63
63
  border: `${border || ''}+${borderString}${i === this.columns.length - 1 ? '+' : ''}`,
@@ -69,17 +69,17 @@ export class Table {
69
69
  console.log(header);
70
70
  console.log(ansi.gray(border));
71
71
 
72
- this.rows.forEach(r => {
72
+ this.rows.forEach((r) => {
73
73
  const rowString = r.reduce((row, cell, i) => {
74
- const width = ansi.strip(cell).length
74
+ const width = ansi.strip(cell).length;
75
75
  const padding = Math.max(0, this.columns[i].width - width - 2);
76
- const paddingString = (new Array(padding + 1)).join(' ');
76
+ const paddingString = new Array(padding + 1).join(' ');
77
77
 
78
- 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('|') : ''}`;
79
79
  }, '');
80
80
 
81
81
  console.log(rowString);
82
82
  console.log(ansi.gray(border));
83
- })
83
+ });
84
84
  }
85
85
  }
package/src/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { parse } from 'yaml';
3
+ import yaml from 'yaml';
4
4
 
5
5
  // Cached config object. The config is stored in here so it can
6
6
  // be loaded once and imported across all modules.
@@ -43,18 +43,14 @@ export async function load(env = null) {
43
43
  if (path.basename(configPath) === 'frak.config.js') {
44
44
  Object.assign(config, (await import(configPath)).default);
45
45
  } else if (path.basename(configPath) === '.frak') {
46
- Object.assign(config, parse(fs.readFileSync(configPath).toString('utf-8')));
46
+ Object.assign(config, yaml.parse(fs.readFileSync(configPath).toString('utf-8')));
47
47
  }
48
48
 
49
49
  if (env in config) {
50
- Object.assign(config, {...config[env]});
50
+ Object.assign(config, { ...config[env] });
51
51
 
52
52
  delete config[env];
53
-
54
- return config;
55
- }
56
-
57
- if (env) {
53
+ } else if (env) {
58
54
  throw new Error(`Missing configuration for environment "${env}"!`);
59
55
  }
60
56
 
@@ -84,4 +80,3 @@ export function reset() {
84
80
  }
85
81
 
86
82
  export default config;
87
-
package/src/date.js CHANGED
@@ -29,28 +29,28 @@ 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 {
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
@@ -16,7 +16,7 @@ export default async function diff(dryRunOutput, options) {
16
16
  const changes = dryRunOutput
17
17
  .split(/\r?\n/)
18
18
  .filter(Boolean)
19
- .map(line => {
19
+ .map((line) => {
20
20
  // Split line into action and file, preserving whitespace for filenames
21
21
  const [action, ...parts] = line.split(/\s+/);
22
22
 
@@ -26,7 +26,7 @@ export default async function diff(dryRunOutput, options) {
26
26
  if (action.match(/^cL.+/)) {
27
27
  const index = parts.indexOf('->');
28
28
 
29
- debug({ parts })
29
+ debug({ parts });
30
30
 
31
31
  file = parts.slice(0, index).join(' ');
32
32
  target = parts.slice(index + 1).join(' ');
@@ -38,12 +38,14 @@ export default async function diff(dryRunOutput, options) {
38
38
  })
39
39
  .filter(([, file]) => file.slice(-1) !== '/');
40
40
 
41
- debug({ changes })
41
+ debug({ changes });
42
42
 
43
43
  let proceed = true;
44
44
 
45
45
  if (changes.length >= 50 && options.interactive) {
46
- proceed = await cli.agree(ansi.yellow(`${changes.length} file(s) will be compared. Continue? `))
46
+ proceed = await cli.agree(
47
+ ansi.yellow(`${changes.length} file(s) will be compared. Continue? `)
48
+ );
47
49
  }
48
50
 
49
51
  if (!proceed) {
@@ -52,53 +54,57 @@ export default async function diff(dryRunOutput, options) {
52
54
 
53
55
  const files = changes.map(([, file]) => file);
54
56
 
55
- const diffs = changes.sort((a, b) => a[1].localeCompare(b[1])).map(change => {
56
- const [action, file] = change;
57
+ const diffs = changes
58
+ .sort((a, b) => a[1].localeCompare(b[1]))
59
+ .map((change) => {
60
+ const [action, file] = change;
57
61
 
58
- // Escape double quotes in file
59
- const fileEscaped = file.replace(/"/g, '\\"');
62
+ // Escape double quotes in file
63
+ const fileEscaped = file.replace(/"/g, '\\"');
60
64
 
61
- const header = [
62
- file.match(/"/) ? `diff --git "a/${fileEscaped}" "b/${fileEscaped}"` : `diff --git a/${file} b/${file}`,
63
- ];
65
+ const header = [
66
+ file.match(/"/)
67
+ ? `diff --git "a/${fileEscaped}" "b/${fileEscaped}"`
68
+ : `diff --git a/${file} b/${file}`,
69
+ ];
64
70
 
65
- let diff = '';
71
+ let diff = '';
66
72
 
67
- if (action === '*deleting') {
68
- header.push('deleted file mode 100644');
69
- header.push('index 0000000..0000000');
73
+ if (action === '*deleting') {
74
+ header.push('deleted file mode 100644');
75
+ header.push('index 0000000..0000000');
70
76
 
71
- diff = `diff -Naurp --label="a/${fileEscaped}" --label=/dev/null "${fileEscaped}" /dev/null`;
72
- } else if (action.match(/^<f\++/)) {
73
- const { mode } = statSync(file);
77
+ diff = `diff -Naurp --label="a/${fileEscaped}" --label=/dev/null "${fileEscaped}" /dev/null`;
78
+ } else if (action.match(/^<f\++/)) {
79
+ const { mode } = statSync(file);
74
80
 
75
- header.push(`new file mode ${mode.toString(8)}`);
76
- header.push('index 0000000..0000000');
81
+ header.push(`new file mode ${mode.toString(8)}`);
82
+ header.push('index 0000000..0000000');
77
83
 
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\++/)) {
80
- const target = change[2];
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];
81
87
 
82
- header.push('new file mode 120000');
83
- header.push('index 0000000..0000000');
88
+ header.push('new file mode 120000');
89
+ header.push('index 0000000..0000000');
84
90
 
85
- diff = `diff -Naurp --label=/dev/null --label="b/${fileEscaped}" /dev/null <(printf %s "${target}")`;
86
- } else if (action.match(/^cL.+/)) {
87
- const target = change[2];
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];
88
94
 
89
- header.push('index 0000000..0000000');
95
+ header.push('index 0000000..0000000');
90
96
 
91
- diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" <(printf %s "$(readlink "${fileEscaped}")") <(printf %s "${target}")`;
92
- } else {
93
- const { mode } = statSync(file);
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
- header.push(`index 0000000..0000000 ${mode.toString(8)}`);
101
+ header.push(`index 0000000..0000000 ${mode.toString(8)}`);
96
102
 
97
- diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" "${fileEscaped}" <(tar xzOf $tarball "${fileEscaped}" || cat /dev/null)`;
98
- }
103
+ diff = `diff -Naurp --label="a/${fileEscaped}" --label="b/${fileEscaped}" "${fileEscaped}" <(tar xzOf $tarball "${fileEscaped}" || cat /dev/null)`;
104
+ }
99
105
 
100
- return `printf "%s\\n" '${header.join('\n')}'; ${diff}`;
101
- });
106
+ return `printf "%s\\n" '${header.join('\n')}'; ${diff}`;
107
+ });
102
108
 
103
109
  if (diffs.length > 0) {
104
110
  debug({ diffs, files });
@@ -111,9 +117,10 @@ export default async function diff(dryRunOutput, options) {
111
117
  'tarball=$(mktemp /tmp/frak-diff-tarball.XXXXXX);',
112
118
  'cat > $tarball;',
113
119
  `cd ${config.root};`,
114
- diffs.join('; '), ';',
120
+ diffs.join('; '),
121
+ ';',
115
122
  'rm $tarball',
116
- ])
123
+ ]);
117
124
  tar.stdout.pipe(ssh.stdin);
118
125
 
119
126
  let less;
@@ -125,22 +132,35 @@ export default async function diff(dryRunOutput, options) {
125
132
  if (options.interactive && options.colorize) {
126
133
  const colorize = spawn('sed', [
127
134
  // headers
128
- '-e', `s/^\\(diff --git .*\\)/${ansi.bold('\\1')}/`,
129
- '-e', `s/^\\(index .*\\)/${ansi.bold('\\1')}/`,
130
- '-e', `s/^\\(new file mode .*\\)/${ansi.bold('\\1')}/`,
131
- '-e', `s/^\\(deleted file mode .*\\)/${ansi.bold('\\1')}/`,
132
- '-e', `s/^\\(--- .*\\)/${ansi.bold('\\1')}/`,
133
- '-e', `s/^\\(+++ .*\\)/${ansi.bold('\\1')}/`,
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')}/`,
134
147
  // additions
135
- '-e', `s/^\\(+[^+].*\\)/${ansi.green('\\1')}/`,
136
- '-e', `s/^\\(+\\)$/${ansi.green('\\1')}/`,
148
+ '-e',
149
+ `s/^\\(+[^+].*\\)/${ansi.green('\\1')}/`,
150
+ '-e',
151
+ `s/^\\(+\\)$/${ansi.green('\\1')}/`,
137
152
  // deletions
138
- '-e', `s/^\\(-[^-].*\\)/${ansi.red('\\1')}/`,
139
- '-e', `s/^\\(-\\)$/${ansi.red('\\1')}/`,
153
+ '-e',
154
+ `s/^\\(-[^-].*\\)/${ansi.red('\\1')}/`,
155
+ '-e',
156
+ `s/^\\(-\\)$/${ansi.red('\\1')}/`,
140
157
  // markers
141
- '-e', `s/^\\(@@ -[0-9][0-9]*,[0-9][0-9]* +[0-9][0-9]*,[0-9][0-9]* .*@@\\)/${ansi.blue('\\1')}/`,
142
- '-e', `s/^\\(@@ -[0-9][0-9]*,[0-9][0-9]* +[0-9][0-9]* .*@@\\)/${ansi.blue('\\1')}/`,
143
- '-e', `s/^\\(@@ -[0-9][0-9]* +[0-9][0-9]*,[0-9][0-9]* .*@@\\)/${ansi.blue('\\1')}/`,
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')}/`,
144
164
  ]);
145
165
  ssh.stdout.pipe(colorize.stdin);
146
166
  colorize.stdout.pipe(less.stdin);
@@ -153,19 +173,19 @@ export default async function diff(dryRunOutput, options) {
153
173
  less.on('exit', (code) => {
154
174
  if (code === 0) {
155
175
  resolve();
156
- /* node:coverage disable */
157
176
  } else {
177
+ /* node :coverage disable */
158
178
  reject(`exited with code ${code}`);
179
+ /* node:coverage enable */
159
180
  }
160
- /* node:coverage enable */
161
181
  });
162
182
  });
163
- /* node:coverage disable */
164
183
  } else {
184
+ /* node:coverage disable */
165
185
  return await new Promise((resolve) => {
166
186
  let diff = '';
167
187
 
168
- ssh.stdout.on('data', (data) => diff += data);
188
+ ssh.stdout.on('data', (data) => (diff += data));
169
189
 
170
190
  ssh.stderr.pipe(process.stderr);
171
191
 
@@ -173,8 +193,8 @@ export default async function diff(dryRunOutput, options) {
173
193
  resolve(diff);
174
194
  });
175
195
  });
196
+ /* node:coverage enable */
176
197
  }
177
- /* node:coverage enable */
178
198
  } else if (options.interactive) {
179
199
  console.log(ansi.yellow('No changes detected.'));
180
200
  }
package/src/rsync.js CHANGED
@@ -32,23 +32,24 @@ export function filters() {
32
32
 
33
33
  // merge path
34
34
  if (options.path) {
35
- const paths = options.path.split(/((?<!\\) )+/)
36
- .map(p => p.replace(/\\ /g, ' '))
37
- .map(p => path.resolve(p))
38
- .map(p => p) // TODO glob // paths.map! { |p| p[/[*?]/] ? Dir.glob(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 }
39
40
  .flat(Infinity)
40
41
  // make paths relative to .frak file
41
- .map(p => p.replace(path.dirname(locateConfig()), ''));
42
+ .map((p) => p.replace(path.dirname(locateConfig()), ''));
42
43
 
43
44
  // include all parent directories to fix recursive rsync from excluding all
44
- paths.forEach(p => {
45
+ paths.forEach((p) => {
45
46
  includes.push(p);
46
47
 
47
48
  while (path.dirname(p) !== '/') {
48
49
  excludes.push(`${path.dirname(p)}/*`);
49
50
 
50
51
  p = path.dirname(p);
51
- includes.push(p)
52
+ includes.push(p);
52
53
  }
53
54
  });
54
55
 
@@ -60,8 +61,8 @@ export function filters() {
60
61
  filters.push('.e .frakignore');
61
62
  }
62
63
 
63
- includes.forEach(include => filters.push(`+ ${include}`));
64
- excludes.forEach(include => filters.push(`- ${include}`));
64
+ includes.forEach((include) => filters.push(`+ ${include}`));
65
+ excludes.forEach((include) => filters.push(`- ${include}`));
65
66
 
66
67
  return filters;
67
68
  }
@@ -90,35 +91,45 @@ export function exec(...options) {
90
91
  }
91
92
 
92
93
  let backup = [];
93
- let backupName = 'name' in commandOptions
94
- ? `${commandOptions.name}_${(new Date()).toISOString()}`
95
- : (new Date()).toISOString();
94
+ let backupName =
95
+ 'name' in commandOptions
96
+ ? `${commandOptions.name}_${new Date().toISOString()}`
97
+ : new Date().toISOString();
96
98
 
97
99
  // Figure out backup options
98
- backup = [
99
- '--exclude=.backups/',
100
- ];
100
+ backup = ['--exclude=.backups/'];
101
101
 
102
102
  // Keep the same backup path for all rsync commands
103
103
  if (backupPath === null) {
104
- backupPath = `.backups/${backupName}`
104
+ backupPath = `.backups/${backupName}`;
105
105
  }
106
106
 
107
107
  // This should only happen on push
108
108
  if (command === 'push') {
109
- backup = [...backup, '--backup', `--backup-dir='${backupPath}'`]
109
+ backup = [...backup, '--backup', `--backup-dir=${backupPath}`];
110
110
  }
111
111
 
112
112
  const args = [
113
113
  'rsync',
114
114
  [
115
- '--archive', '--no-group', '--no-owner', '--no-perms', '--no-times', '--human-readable',
116
- '--compress', '--checksum', '--itemize-changes', '--recursive', '--delete',
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',
117
127
  `--rsync-path=${config['rsync_path'] ? config['rsync_path'] : 'rsync'}`,
118
128
  '--filter=. -',
119
129
  ...backup,
120
130
  ...options,
121
- source, destination,
131
+ source,
132
+ destination,
122
133
  ],
123
134
  ];
124
135
 
@@ -126,17 +137,21 @@ export function exec(...options) {
126
137
  debug(args.flat().join(' '));
127
138
  debug('filters:', filters());
128
139
 
129
- let output = '';
140
+ let output = '',
141
+ stderr = '';
130
142
 
131
143
  const rsync = spawn.apply(null, args);
132
144
 
133
- const echo = spawn('echo', [ filters().join('\n') ]);
145
+ const echo = spawn('echo', [filters().join('\n')]);
134
146
 
135
147
  echo.stdout.pipe(rsync.stdin);
136
148
 
137
149
  rsync.stdout.on('data', (data) => {
138
150
  output += `${data}`;
139
151
  });
152
+ rsync.stderr.on('data', (data) => {
153
+ stderr += `${data}`;
154
+ });
140
155
 
141
156
  /* node:coverage disable */
142
157
  rsync.stderr.on('data', (data) => {
@@ -151,11 +166,11 @@ export function exec(...options) {
151
166
  output,
152
167
  backupPath,
153
168
  });
154
- /* node:coverage disable */
155
169
  } else {
156
- reject(`rsync exited with code ${code}`);
170
+ /* node:coverage disable */
171
+ reject(`rsync exited with code ${code}: ${stderr}`);
172
+ /* node:coverage enable */
157
173
  }
158
- /* node:coverage enable */
159
174
  });
160
175
  });
161
176
  }
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', [ options.server, '--', cmd ]);
9
+ const ssh = spawn('ssh', [options.server, '--', cmd]);
10
10
 
11
11
  // const echo = spawn('echo', [ command ]);
12
12
 
@@ -31,11 +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 */
35
34
  } else {
35
+ /* node:coverage disable */
36
36
  reject(`exited with code ${code}`);
37
+ /* node:coverage enable */
37
38
  }
38
- /* node:coverage enable */
39
39
  });
40
40
  });
41
41