@tangelo/tangelo-configuration-toolkit 1.20.2 → 1.21.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/index.js CHANGED
@@ -72,6 +72,7 @@ global._git = {
72
72
  if (!_appdata.gitUser) _appdata._update({gitUser: execGitCommand(`config --get user.email`, _paths.repo)});
73
73
  return _appdata.gitUser;
74
74
  },
75
+ status: memoize(() => execGitCommand(`status -s`, _paths.repo)),
75
76
  commitLocal: memoize(() => execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, _paths.repo, ['branch', 'hash', 'date'])),
76
77
  commitRemote: memoize(() => execGitCommand('log -1 --format=%cd --date=iso-strict origin/' + _git.commitLocal().branch, _paths.repo, ['date'])),
77
78
  commitTdi: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangelo/tangelo-configuration-toolkit",
3
- "version": "1.20.2",
3
+ "version": "1.21.0",
4
4
  "engines": {
5
5
  "node": ">=14.0.0"
6
6
  },
@@ -21,13 +21,15 @@ module.exports = function(ftpConfig, remotedir) {
21
21
  return cb();
22
22
  },
23
23
 
24
- function flush(cb) {
24
+ async function flush(cb) {
25
25
  if (!files[0]) cb();
26
26
 
27
27
  const paths = [...new Set(files.map(({destination}) => path.dirname(destination)))] // collect unique paths
28
28
  .filter((p1, i, a) => !a.find(p2 => p1 != p2 && p2.includes(p1+'/'))) // remove paths being part of others
29
29
  ;
30
30
 
31
+ await ftpConfig.eventBeforeAll(files);
32
+
31
33
  sftp.connect(ftpConfig)
32
34
  .then(() => Promise.all(paths.map(p => parallel( // check if all directories exist
33
35
  () => sftp.exists(p).then(r => {
@@ -48,8 +50,8 @@ module.exports = function(ftpConfig, remotedir) {
48
50
  ).catch(err => _warn(`File transfer failed${err ? ': '+err : ''}`));
49
51
  }
50
52
  ))))
51
- .then(() => {
52
- _info(`${files.length} file(s) transferred`);
53
+ .then(async () => {
54
+ await ftpConfig.eventAfterAll(files);
53
55
  cb();
54
56
  return sftp.end();
55
57
  })
@@ -0,0 +1,44 @@
1
+ const {NodeSSH} = require('node-ssh');
2
+ const pLimit = require('p-limit');
3
+
4
+
5
+ module.exports = class RemoteExec {
6
+
7
+ #ftpConfig;
8
+ #queue = [];
9
+
10
+ constructor(ftpConfig) {
11
+ this.#ftpConfig = ftpConfig;
12
+ }
13
+
14
+ add(cmd, msg) {
15
+ if (!this.#queue.find(e => e[0]==cmd)) this.#queue.push([cmd, msg]);
16
+ return this;
17
+ }
18
+
19
+ async process() {
20
+ if (!this.#queue[0]) return;
21
+
22
+ const sshI = new NodeSSH();
23
+ const limit = pLimit(this.#ftpConfig.parallel);
24
+
25
+ await sshI.connect(this.#ftpConfig) // set up connection once
26
+ .then(() => Promise.all(this.#queue.map(([cmd, msg]) => limit(
27
+ () => sshI.execCommand(cmd).then(result => {
28
+ if (result.stderr) _warn(result.stderr);
29
+ else if (msg === 'STDOUT') _write(result.stdout);
30
+ else if (msg) _info(msg === 'CMD' ? cmd : msg);
31
+ })
32
+ ))))
33
+ .catch(err => {
34
+ if (err.code=='ECONNRESET' || err.code=='ECONNABORTED') _warn(`Server aborted connection. Retrying.`);
35
+ else _warn(err);
36
+ this.process(); // retry set up connection
37
+ })
38
+ .finally(() => {
39
+ sshI.dispose();
40
+ this.#queue = [];
41
+ });
42
+ }
43
+
44
+ };
@@ -3,7 +3,6 @@ const execGitCommand = require('../../lib/exec-git-command');
3
3
  const fs = require('fs-extra');
4
4
  const globby = require('globby');
5
5
  const gulp = require('gulp');
6
- const {NodeSSH} = require('node-ssh');
7
6
  const g_tcl = require('../../lib/gulp-tcl-config');
8
7
  const through2 = require('through2');
9
8
  const {spawnSync} = require('child_process');
@@ -24,59 +23,6 @@ const c = require('./config');
24
23
  const s = require('./srcset');
25
24
 
26
25
 
27
- const remote = {
28
- queue: [],
29
- add(cmd, msg) {
30
- if (!this.queue.find(e => e[0]==cmd)) this.queue.push([cmd, msg]);
31
- return this;
32
- },
33
- do(sshI) {
34
- let executing = Math.min(this.queue.length, c.server.ftpConfig.parallel);
35
-
36
- if (executing===0) { // close connection and return callback after last exec command
37
- sshI.dispose();
38
- _write();
39
- }
40
-
41
- // execute each command from the queue in batches
42
- this.queue.filter((e, i) => i < executing).forEach(()=>{
43
- const command = this.queue.shift();
44
-
45
- sshI.execCommand(command[0])
46
- .then(result => {
47
- if (result.stderr) _warn(result.stderr);
48
- else if (command[1] === '') _write(result.stdout);
49
- else _info(command[1] || command[0], true);
50
-
51
- executing--;
52
- if (executing===0) this.do(sshI);
53
- })
54
- .catch(() => {
55
- _warn(`Server aborted connection. Retrying.`);
56
- this.queue.unshift(command);
57
-
58
- executing--;
59
- if (executing===0) this.do(sshI);
60
- });
61
- });
62
- },
63
- process() {
64
- if (!this.queue[0]) return;
65
-
66
- const sshI = new NodeSSH();
67
- sshI.connect(c.server.ftpConfig) // set up connection once
68
- .then(() => {
69
- this.do(sshI);
70
- })
71
- .catch(err => {
72
- if (err.code=='ECONNRESET' || err.code=='ECONNABORTED') _warn(`Server aborted connection. Retrying.`);
73
- else _warn(err);
74
- this.process(); // retry set up connection
75
- });
76
- }
77
- };
78
-
79
-
80
26
  const createDeliveryPack = () => { // create install scripts if necessary, zip output and remove temp folder
81
27
  const dbiSql = [];
82
28
  const dbiPs1 = [];
@@ -120,7 +66,7 @@ const createDeliveryPack = () => { // create install scripts if necessary, zip o
120
66
  };
121
67
 
122
68
 
123
- const transfer = (paths, {watch, lrServer} = {}) => {
69
+ module.exports = function transfer (paths, {watch, lrServer} = {}) {
124
70
  const jsFGlob = _settingsTdi().transpileToES5 ? ['**/*.js', '!**/hce/**/template_*', '!**/fonto/**', '!**/vendor/**', '!**/*.min.js'] : [];
125
71
  const jsF = g_filter(jsFGlob, {restore: true});
126
72
  const xpsF = g_filter(['**/xopus/**/*.x*'], {restore: true});
@@ -189,8 +135,6 @@ const transfer = (paths, {watch, lrServer} = {}) => {
189
135
  _info('Finished transferring\n', true);
190
136
  if (fontoPaths.outdated[0]) _warn(`Fonto build files in the following folders were outdated and therefore skipped:\n ${fontoPaths.outdated.map(v => v.slice(0, -1)).join('\n ')}`);
191
137
 
192
- remote.process();
193
-
194
138
  if (lrServer) { // reload specific resources if files are all image or css, else reload page
195
139
  const reloadPage = !files.every(f => /.(?:css|jpe?g|png|gif)$/i.test(f));
196
140
  lrServer.changed({params: {files: reloadPage ? ['page'] : files}});
@@ -199,7 +143,4 @@ const transfer = (paths, {watch, lrServer} = {}) => {
199
143
 
200
144
  if (c.deliveryPack) createDeliveryPack();
201
145
  });
202
- };
203
-
204
-
205
- module.exports = {remote, transfer};
146
+ };
@@ -1,10 +1,11 @@
1
1
  const del = require('del');
2
2
  const gulp = require('gulp');
3
3
  const path = require('path');
4
+ const RemoteExec = require('../../lib/remote-exec');
4
5
  const sftpClient = require('ssh2-sftp-client');
5
6
  const tinylr = require('tiny-lr');
6
7
 
7
- const {remote, transfer} = require('./execute');
8
+ const transfer = require('./execute');
8
9
  const c = require('./config');
9
10
  const s = require('./srcset');
10
11
 
@@ -16,17 +17,34 @@ module.exports = function deploy (argv) {
16
17
  c.setServer(argv.server);
17
18
  c.prepareForCopy(argv.filter);
18
19
 
19
- if (c.server.ftpConfig) {
20
- c.server.ftpConfig.eventPut = file => {
21
- _write(file.destination.replace(c.server.remotedir, ''));
20
+ const {ftpConfig, remotedir} = c.server;
21
+
22
+ if (ftpConfig) {
23
+ const logPath = path.join(remotedir, 'log/deployments.log').toFws;
24
+ const re = new RemoteExec(ftpConfig);
25
+
26
+ ftpConfig.eventPut = file => {
27
+ _write(file.destination.replace(remotedir, ''));
22
28
  if (path.extname(file.destination)=='.sh')
23
- remote.add('chmod 755 '+file.destination, 'Permissions set: '+file.destination);
29
+ re.add('chmod 755 '+file.destination, 'Permissions set: '+file.destination);
24
30
  else if (file.destination.match(/cmscustom.*hce.*config.xml/))
25
- remote.add('touch '+c.getRemotePath('hce/hce-config.xml'), 'Touched: '+c.getRemotePath('hce/hce-config.xml'));
31
+ re.add('touch '+c.getRemotePath('hce/hce-config.xml'), 'Touched: '+c.getRemotePath('hce/hce-config.xml'));
26
32
  else if (file.destination.match(/cmscustom.*od[fts].*config.xml/))
27
- remote.add('touch '+c.getRemotePath('odf/odf-config.xml'), 'Touched: '+c.getRemotePath('odf/odf-config.xml'));
33
+ re.add('touch '+c.getRemotePath('odf/odf-config.xml'), 'Touched: '+c.getRemotePath('odf/odf-config.xml'));
28
34
  else if (file.destination.match(/txp\/site-configs/))
29
- remote.add('touch '+c.getRemotePath('txp/xmlpages-config.xml'), 'Touched: '+c.getRemotePath('txp/xmlpages-config.xml'));
35
+ re.add('touch '+c.getRemotePath('txp/xmlpages-config.xml'), 'Touched: '+c.getRemotePath('txp/xmlpages-config.xml'));
36
+ };
37
+
38
+ ftpConfig.eventBeforeAll = async files => {
39
+ const dli = deployLogInfo(!argv.copy, c.transferPatterns[0], files, 'START');
40
+ await re.add(`echo -e "${dli}" >> ${logPath}`).process();
41
+ };
42
+
43
+ ftpConfig.eventAfterAll = async files => {
44
+ _info(`${files.length} file(s) transferred`);
45
+ const dli = deployLogInfo(!argv.copy, c.transferPatterns[0], files, 'DONE');
46
+ re.add(`echo -e "${dli}" >> ${logPath}`).add(`echo "$(tail -10000 ${logPath})" > ${logPath}`);
47
+ await re.process();
30
48
  };
31
49
  }
32
50
 
@@ -64,9 +82,9 @@ module.exports = function deploy (argv) {
64
82
  };
65
83
 
66
84
  // check connection
67
- if (c.server.ftpConfig) {
85
+ if (ftpConfig) {
68
86
  const sftp = new sftpClient();
69
- sftp.connect(c.server.ftpConfig).then(() => sftp.end()).catch(err => _error(`Could not connect to server${err ? ': '+err.message : ''}`));
87
+ sftp.connect(ftpConfig).then(() => sftp.end()).catch(err => _error(`Could not connect to server${err ? ': '+err.message : ''}`));
70
88
  }
71
89
 
72
90
  gulp.watch(c.transferPatterns)
@@ -89,8 +107,8 @@ module.exports = function deploy (argv) {
89
107
 
90
108
  if (!path.parse(filepath).base.match(/\.scss/)) {
91
109
  const rp = c.getRemotePath(filepath);
92
- const msg = 'Removed: ' + rp.replace(c.server.remotedir, '').white;
93
- if (c.server.ftpConfig) remote.add(`rm -rf "${rp}"`, msg).process();
110
+ const msg = 'Removed: ' + rp.replace(remotedir, '').white;
111
+ if (ftpConfig) new RemoteExec(ftpConfig).add(`rm -rf "${rp}"`, msg).process();
94
112
  else del([rp], {force: true}).then(() => _info(msg, true));
95
113
  }
96
114
  }
@@ -98,4 +116,46 @@ module.exports = function deploy (argv) {
98
116
 
99
117
  }
100
118
 
101
- };
119
+ };
120
+
121
+
122
+ function deployLogInfo (watch, filter, files, action) {
123
+ const timestamp = new Date().toISOString();
124
+ const user = _git.user().split('@')[0];
125
+ const {branch, hash} = _git.commitLocal();
126
+ const uncommittedChanges = _git.status().trim() ? ':~' : '';
127
+
128
+ let logline = `${timestamp} ${action.padEnd(5)} [${user}] [${branch}:${hash.substring(0, 7)}${uncommittedChanges}] [${filter}]`;
129
+
130
+ if (action == 'START') {
131
+ if (uncommittedChanges) {
132
+ const uncommittedChanges = _git.status().replace(/\?\?/g, ' U').split('\n');
133
+ logline += '\n Uncommitted changes:\n ' + uncommittedChanges.join('\n ');
134
+ }
135
+ const filepaths = files.map(({destination}) => destination);
136
+ logline += `\n Transferring ${filepaths.length} file${filepaths.length > 1 ? 's' : ''}`;
137
+ if (watch || filepaths.length == 1) logline += ':\n ' + filepaths.join('\n ');
138
+ else logline += ' in dir:\n ' + getCommonPath(filepaths);
139
+ }
140
+ return logline.replace(/"/g, '\\"');
141
+ }
142
+
143
+
144
+ function getCommonPath(paths) {
145
+ const splitPaths = paths.map(p => p.split('/'));
146
+ let commonPath = splitPaths[0];
147
+
148
+ for (let i = 1; i < splitPaths.length; i++) {
149
+ const pathParts = splitPaths[i];
150
+ commonPath = commonPath.slice(0, Math.min(commonPath.length, pathParts.length));
151
+
152
+ for (let j = 0; j < commonPath.length; j++) {
153
+ if (commonPath[j] !== pathParts[j]) {
154
+ commonPath = commonPath.slice(0, j);
155
+ break;
156
+ }
157
+ }
158
+ }
159
+
160
+ return commonPath.length === 0 ? '' : commonPath.join('/');
161
+ }
@@ -6,7 +6,7 @@ const {Table} = require('console-table-printer');
6
6
  const execGitCommand = require('../../lib/exec-git-command');
7
7
  const getTdiBranch = require('../../lib/get-tdi-branch');
8
8
  const c = require('../deploy/config');
9
- const {remote} = require('../deploy/execute');
9
+ const RemoteExec = require('../../lib/remote-exec');
10
10
 
11
11
 
12
12
  const getGitInfo = () => {
@@ -142,7 +142,7 @@ const getServerInfo = (server) => {
142
142
 
143
143
  if (!c.envDev) {
144
144
  _info(`Remote version info for '${c.server.ftpConfig.host}':\n`);
145
- remote.add('sudo ~root/scripts/version.sh', '').process();
145
+ new RemoteExec(c.server.ftpConfig).add('sudo ~root/scripts/version.sh', 'STDOUT').process();
146
146
  }
147
147
  else {
148
148
  _info('For development environments no server version information is available. Check rancher / database for this information.\nAdd the --server option with a non-dev environment to see version information for that server.');