@tangelo/tangelo-configuration-toolkit 1.12.1 → 1.14.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/.eslintrc.js CHANGED
@@ -14,6 +14,7 @@ module.exports = {
14
14
  '_write': 'writable',
15
15
  '_info': 'writable',
16
16
  '_warn': 'writable',
17
+ '_formatDate': 'writable',
17
18
  '_error': 'writable',
18
19
  '_perf': 'writable',
19
20
  '_paths': 'writable',
package/LICENSE.md CHANGED
@@ -1 +1 @@
1
- Copyright © 2020 Tangelo Software BV <info@tangelo-software.com>
1
+ Copyright © 2020 Tangelo Software BV <mailto:info@tangelo-software.com>
package/README.md CHANGED
@@ -64,4 +64,4 @@ For a new repository, using `tct build --init` also creates the repoconfig-file.
64
64
  The `build -x` commands set projects transformation scenarios and masterfiles in the oXygen project file with the following functionality:
65
65
  - Will try to preserve manually added entries in the transformation scenarios and masterfiles
66
66
  - Will remove non existing masterfiles or masterfiles that start with a '_'
67
- - No masterfiles / scenarios will be added if their path match with oXygens hidden directory patterns
67
+ - No masterfiles / scenarios will be added if their path match with oXygens hidden directory patterns
@@ -0,0 +1,32 @@
1
+ image: atlassian/default-image:4
2
+
3
+ clone:
4
+ depth: full # SonarCloud scanner needs the full history to assign issues properly
5
+
6
+ definitions:
7
+ caches:
8
+ sonar: ~/.sonar/cache # Caching SonarCloud artifacts will speed up your build
9
+ services:
10
+ docker:
11
+ memory: 2048 # For large file line code
12
+ steps:
13
+ - step: &build-test-sonarcloud
14
+ name: Build, test and analyze on SonarCloud
15
+ caches:
16
+ - sonar
17
+ script:
18
+ - pipe: sonarsource/sonarcloud-scan:1.4.0
19
+ - step: &check-quality-gate-sonarcloud
20
+ name: Check the Quality Gate on SonarCloud
21
+ script:
22
+ - pipe: sonarsource/sonarcloud-quality-gate:0.1.6
23
+
24
+ pipelines:
25
+ branches:
26
+ master:
27
+ - step: *build-test-sonarcloud
28
+ - step: *check-quality-gate-sonarcloud
29
+ pull-requests:
30
+ '**':
31
+ - step: *build-test-sonarcloud
32
+ - step: *check-quality-gate-sonarcloud
package/index.js CHANGED
@@ -24,7 +24,7 @@ global._write = (...msg) => {
24
24
  global._info = (msg, time = false) => {
25
25
  if (time) {
26
26
  const tzDiff = new Date().getTimezoneOffset() * 60000;
27
- const time = new Date(Date.now() - tzDiff).toISOString().replace(/.*T(.*)\..*/g, '$1');
27
+ const time = new Date(Date.now() - tzDiff).toISOString().match(/\d\d:\d\d:\d\d/)[0];
28
28
  msg = `[${time}] ${msg}`;
29
29
  }
30
30
  console.log(msg.lgreen);
@@ -44,6 +44,9 @@ global._perf = (t1) => {
44
44
  console.log(`\nExecution time: ${t2 - t1}ms`.blue);
45
45
  };
46
46
 
47
+ global._formatDate = (date) => {
48
+ return date.toLocaleDateString('en-gb', {day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'});
49
+ };
47
50
 
48
51
  global._paths = {
49
52
  app: __dirname,
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@tangelo/tangelo-configuration-toolkit",
3
- "version": "1.12.1",
3
+ "version": "1.14.0",
4
+ "engines": {
5
+ "node": ">=14.0.0"
6
+ },
4
7
  "description": "Tangelo Configuration Toolkit is a command-line toolkit which offers support for developing a Tangelo configuration.",
5
8
  "bin": {
6
9
  "tct": "bin/index.js",
package/src/cli.js CHANGED
@@ -64,9 +64,11 @@ module.exports = function cli () {
64
64
  aliases: ['g'],
65
65
  desc: 'Git repo actions (requires global git installation)',
66
66
  builder: {
67
- init: {alias: 'i', desc: 'Initialize repository and add submodule', conflicts: ['r']},
68
- reset: {alias: 'r', desc: 'Reset repository to last commit', conflicts: ['i']},
69
- clone: {alias: 'c', desc: 'Clone a client repository and do basic setup'},
67
+ init: {alias: 'i', desc: 'Initialize repository and add submodule', conflicts: ['r', 'c', 'u']},
68
+ reset: {alias: 'r', desc: 'Reset repository to last commit', conflicts: ['i', 'c', 'u']},
69
+ clone: {alias: 'c', desc: 'Clone a client repository and do basic setup', conflicts: ['i', 'r', 'u']},
70
+ update: {alias: 'u', desc: 'Update repository or TDI submodule', conflicts: ['i', 'r', 'c']},
71
+ branch: {alias: 'b', desc: 'Pass tdi release branch, e.g. "5.4"', conflicts: ['i', 'r', 'c']}
70
72
  },
71
73
  handler: require('./modules/git')
72
74
  })
@@ -92,15 +94,17 @@ module.exports = function cli () {
92
94
  remove: {alias: 'r', desc: 'Remove project configuration', conflicts: ['i', 'c', 'g']}
93
95
  },
94
96
  handler: require('./modules/sql')
95
- }).command({
96
- command: 'versions',
97
- aliases: ['v'],
98
- desc: 'Show version information',
97
+ })
98
+ .command({
99
+ command: 'info',
100
+ aliases: ['i'],
101
+ desc: 'Show project information',
99
102
  builder: {
100
- find: {alias: 'f', desc: `Find all version information [choices: "sort-project"(default),"sort-type"]`},
103
+ doctypes: {alias: 'd', desc: `List document-types information`},
104
+ versions: {alias: 'v', desc: `Find all version information, sorted by project or type`, choices: ['project', 'type'], conflicts: ['d']},
101
105
  server: {alias: 's', desc: 'Pass server name (set in config file)', default: _appconfig.defaultServer}
102
106
  },
103
- handler: require('./modules/version')
107
+ handler: require('./modules/info')
104
108
  })
105
109
  .recommendCommands()
106
110
  .option('config', {alias: 'c', desc: 'Show loaded appconfig', global: false})
@@ -109,10 +113,13 @@ module.exports = function cli () {
109
113
  .example([
110
114
  ['$0 deploy --copy --server demo', 'Copy files to server "demo"'],
111
115
  ['$0 d -c -s demo', 'Same as above'],
112
- ['$0 f -sb && $0 d -c', 'Compile schema and build Fonto, then deploy to default server']
116
+ ['$0 f -sb && $0 d -c', 'Compile schema and build Fonto, then deploy to default server'],
117
+ ['$0 git --update', 'Pull git repository and submodule to latest repository commit'],
118
+ ['$0 git --update tdi', 'Update TDI submodule to latest within current TDI branch'],
119
+ ['$0 git --update tdi --branch 5.4', 'Update TDI submodule to latest in specified branch']
113
120
  ])
114
121
  .check((argv, options) => {
115
- const passedOpts = Object.keys(argv).filter(k => !['_', '$0', 'server', 's', 'filter', 'f'].includes(k)); // also filter options with defaults
122
+ const passedOpts = Object.keys(argv).filter(k => !['_', '$0', 'server', 's', 'filter', 'f', 'branch', 'b'].includes(k)); // also filter options with defaults
116
123
  const hasOpts = Object.keys(options)[0];
117
124
  if (hasOpts && !passedOpts[0]) yargs.showHelp();
118
125
  return true;
@@ -1,14 +1,15 @@
1
1
  const {spawnSync} = require('child_process');
2
2
 
3
3
 
4
- module.exports = function execGitCommand(args, path, returnProperties = [], expectedStatus) {
4
+ module.exports = function execGitCommand(args, path, returnProperties, expectedStatus) {
5
5
 
6
6
  const cmd = spawnSync('git', [args], {cwd: path, shell: true});
7
7
 
8
+ if (cmd.status !== 0) return {error: `${cmd.stderr}`};
8
9
  if (expectedStatus!=undefined) return cmd.status == expectedStatus;
9
10
 
10
11
  let retObj = (cmd.stdout||'').toString().trim().split(';');
11
- if (returnProperties[0]) {
12
+ if (returnProperties?.[0]) {
12
13
  const o = {};
13
14
  retObj = (
14
15
  retObj.forEach((v, i) => {
@@ -21,6 +22,6 @@ module.exports = function execGitCommand(args, path, returnProperties = [], expe
21
22
  o
22
23
  );
23
24
  }
24
- return retObj.length==1 ? retObj[0] : retObj;
25
+ return retObj.length === 1 ? retObj[0] : retObj;
25
26
 
26
27
  };
@@ -16,7 +16,7 @@ module.exports = function getRepoconfig() {
16
16
  const ntName = ntSqlInsert.match(/'([^']+)' name/)[1];
17
17
  const xpiDir = ntSqlInsert.match(/'([^']+)' xsl_prep_inc/)[1];
18
18
 
19
- const path_cmscustom = xpiDir.replace(/\/[^/]+$/, '').split(/[/]/);
19
+ const path_cmscustom = xpiDir.replace(/\/[^/]+$/, '').split(/\//);
20
20
  const path_dbconfig = p.replace(/database\/config\/(.*)\/[^/]+/, '$1').split(/[/\\]/);
21
21
  const dnRegex = [path_cmscustom[0].replace(/[_'"]/g, '.'), path_dbconfig[0].replace(/[_'"]/g, '.')].sort((a, b) => b.length - a.length).join('|');
22
22
  const customer_name = (dtDisplayName.match(RegExp(dnRegex, 'i')) || dtDisplayName.split(/ (.+)/))[0];
@@ -0,0 +1,41 @@
1
+ const execGitCommand = require('./exec-git-command');
2
+ const path = require('path');
3
+
4
+ module.exports = function getTdiBranch(toBranchName) {
5
+ const tdiBranch = {};
6
+ let toBranch;
7
+ if (toBranchName) {
8
+ // Check if specified branch exists; we will update to this branch
9
+ toBranch = String(toBranchName).replace(/(?:release\/)?(\d+(?:\.[\dx]+)*)/, `release/$1`);
10
+ const branchExists = execGitCommand(`branch --remote`, path.join(_paths.repo, _paths.tdi)).match(`origin/${toBranch}`);
11
+ if (!branchExists) _error(`TDI branch "${toBranch}" does not exist. Note that TCT can only update to a release branch.`);
12
+ }
13
+
14
+ // Get the current TDI branch:
15
+
16
+ // Get remote release branches containing TDI HEAD commit
17
+ const releaseBranches = execGitCommand(`branch --all --contains ${_git.commitTdi.local().hash}`, path.join(_paths.repo, _paths.tdi)).match(/remotes\/origin\/release\/[^\s]+/gsm);
18
+ if (!releaseBranches || releaseBranches.error) _error(`Could not retrieve TDI release branches`);
19
+
20
+ // Get the first possible branch; prefer release/5.1 over release/5.2:
21
+ releaseBranches.sort((a, b) => a > b ? 1 : -1);
22
+
23
+ // Set branch name of firstBranch without 'remotes/origin/'
24
+ tdiBranch.name = releaseBranches[0].replace(/remotes\/origin\//g, '');
25
+
26
+ // In case of branch switch set from.name to the old branch and name to the new branch
27
+ if (toBranch) {
28
+ if (tdiBranch.name > toBranch) _error(`You cannot downgrade to a lower release branch with TCT.`);
29
+ tdiBranch.from = {name: tdiBranch.name};
30
+ tdiBranch.name = toBranch;
31
+
32
+ const branchHash = execGitCommand(`rev-parse origin/${toBranch}`, path.join(_paths.repo, _paths.tdi));
33
+ const commonAncestorHash = execGitCommand(`merge-base ${_git.commitTdi.local().hash} ${branchHash}`, path.join(_paths.repo, _paths.tdi));
34
+ const commonAncestorDate = execGitCommand(`show ${commonAncestorHash} --no-patch --format=%cd --date=iso-strict `, path.join(_paths.repo, _paths.tdi), ['date']).date;
35
+ tdiBranch.commonAncestor = {date: new Date(commonAncestorDate)};
36
+ }
37
+
38
+ // Get number of commits behind
39
+ tdiBranch.commitsBehind = execGitCommand(`rev-list HEAD...origin/${tdiBranch.name} --count`, path.join(_paths.repo, _paths.tdi));
40
+ return tdiBranch;
41
+ };
@@ -15,8 +15,8 @@ const resolveImports = (fp, file) => {
15
15
  fs.readFileSync(xiPath).toString()
16
16
  .replace(/[\s\S]*?<x:config[^>]*>([\s\S]*?)<\/x:config>/, '$1') // only return contents of x:config element
17
17
  .replace(/<(\w+)\/>/g, '<$1></$1>') // self-closing html tags can cause problems
18
- .replace(/url="(\.\.\/)+pls\//g, 'url="#{plsservlet}') // replace relative references to plsservlet by bind (4.1 compatibility)
19
- .replace(/url="(\.\.\/)+/g, 'url="#{cmspath}') // replace relative references to cmspath by bind (4.1 compatibility)
18
+ .replace(/url="(?:\.\.\/)+pls\//g, 'url="#{plsservlet}') // replace relative references to plsservlet by bind (4.1 compatibility)
19
+ .replace(/url="(?:\.\.\/)+/g, 'url="#{cmspath}') // replace relative references to cmspath by bind (4.1 compatibility)
20
20
  .replace(/(<x:javascript\s+src=")([^#].*?)("\s*\/>)/g, (match, g1, g2, g3) => { // correct all javascript refs to paths relative to cmscustompath bind
21
21
  const newPath = path.resolve(fp.dir, path.resolve(path.dirname(xiPath), g2))
22
22
  .replace(/.*cmscustom./, '#{cmscustompath}')
@@ -54,6 +54,7 @@ const resolveIncludes = (fp, file) => {
54
54
  }
55
55
  catch (e) {
56
56
  console.log('Illegal reference to path: '.red + e.path);
57
+ return match;
57
58
  }
58
59
  });
59
60
  }
@@ -2,18 +2,19 @@ const {compare} = require('compare-versions');
2
2
  const exec = require('util').promisify(require('child_process').exec);
3
3
 
4
4
 
5
- const doUpdateCheck = (package) => (
6
- exec(`npm view -g ${_packages[package].name} version`)
5
+ const doUpdateCheck = (pkg) => (
6
+ exec(`npm view -g ${_packages[pkg].name} version`)
7
7
  .then(r => {
8
8
  const versionAvailable = r.stdout.match(/([\d/.]+)/)[1];
9
- if (!_packages[package].version) _warn(`${package} is not installed! Run ` + `npm i -g ${_packages[package].name}`.white);
10
- else if (compare(_packages[package].version, versionAvailable, '<')) {
11
- _appdata._update({[`updateCheck${package}`]: {executed: new Date(), versionAvailable}});
9
+ if (!_packages[pkg].version) _warn(`${pkg} is not installed! Run ` + `npm i -g ${_packages[pkg].name}`.white);
10
+ else if (compare(_packages[pkg].version, versionAvailable, '<')) {
11
+ _appdata._update({[`updateCheck${pkg}`]: {executed: new Date(), versionAvailable}});
12
12
  return versionAvailable;
13
13
  }
14
- else _appdata._update({[`updateCheck${package}`]: {executed: new Date()}});
14
+ else _appdata._update({[`updateCheck${pkg}`]: {executed: new Date()}});
15
+ return;
15
16
  })
16
- .catch(e => _warn(`Failed checking latest version of ${package}.`))
17
+ .catch(() => _warn(`Failed checking latest version of ${pkg}.`))
17
18
  );
18
19
 
19
20
  let checkUpdatesDone = false;
@@ -23,16 +24,16 @@ module.exports = function packageUpdateCheck () {
23
24
  if (!checkUpdatesDone) { // check if updatecheck has ran before because async calls below trigger beforeExit again
24
25
  checkUpdatesDone = true;
25
26
 
26
- ['TCT'].forEach(p => {
27
- const updateMsg = (va) => `| Update ${p} to ${va} | ` + `npm i -g ${_packages[p].name}`.white;
28
- const {versionAvailable} = _appdata[`updateCheck${p}`] || {};
27
+ ['TCT'].forEach(pkg => {
28
+ const updateMsg = (va) => `| Update ${pkg} to ${va} | ` + `npm i -g ${_packages[pkg].name}`.white;
29
+ const {versionAvailable} = _appdata[`updateCheck${pkg}`] || {};
29
30
 
30
- if (new Date() - new Date(_appdata[`updateCheck${p}`]?.executed || 0) > 1000*3600*24*7) { // check every week
31
- doUpdateCheck(p).then(r => r && _warn(updateMsg(r)));
31
+ if (new Date() - new Date(_appdata[`updateCheck${pkg}`]?.executed || 0) > 1000*3600*24*7) { // check every week
32
+ doUpdateCheck(pkg).then(r => r && _warn(updateMsg(r)));
32
33
  }
33
34
  else if (versionAvailable) {
34
- if (compare(_packages[p].version, versionAvailable, '<')) _warn(updateMsg(versionAvailable));
35
- else _appdata._update({[`updateCheck${p}`]: {executed: new Date()}});
35
+ if (compare(_packages[pkg].version, versionAvailable, '<')) _warn(updateMsg(versionAvailable));
36
+ else _appdata._update({[`updateCheck${pkg}`]: {executed: new Date()}});
36
37
  }
37
38
  });
38
39
  }
@@ -9,5 +9,5 @@ for (const name in styles ) {
9
9
  }
10
10
 
11
11
  String.prototype.__defineGetter__('nostyle', function () {
12
- return this.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
12
+ return this.replace(/[\u001b\u009b][[()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
13
13
  });
@@ -59,7 +59,7 @@ const getTransformations = (config) => {
59
59
  // extract baseStrings from file
60
60
  const fileString = fs.readFileSync(f).toString();
61
61
  const baseStrings = fileString.match(RegExp(config.extracts.base, 'gm'));
62
-
62
+
63
63
  if (fileString.replace(/\s|^prompt\s.*$/gm,'')!=='') {
64
64
  if (baseStrings) {
65
65
  baseStrings.forEach(s => {
@@ -156,9 +156,9 @@ const transformXprFile = (xprFiles) => {
156
156
 
157
157
 
158
158
  module.exports = function oxygen () {
159
- // _info(`Set projects transformation scenarios and masterfiles in oXygen project file...`);
160
- // _info(`- Will try to preserve manually added entries in the transformation scenarios and masterfiles`);
161
- // _info(`- Will remove non existing masterfiles or masterfiles that start with a '_'\n`);
159
+ // Set projects transformation scenarios and masterfiles in oXygen project file
160
+ // - Will try to preserve manually added entries in the transformation scenarios and masterfiles
161
+ // - Will remove non existing masterfiles or masterfiles that start with a '_'
162
162
 
163
163
  if (!fs.existsSync(spConfigPath) || !fs.existsSync(sefFilePath)) {
164
164
  _error(`Cannot find required files in TDI submodule. Try updating TDI submodule.`);
@@ -91,7 +91,7 @@ module.exports = {
91
91
  let transferPattern = path.join(_paths.apply, filter).toFws();
92
92
 
93
93
  // test if 'cmscustom/tdi' would be included, then add specifically, because following symlinks doesnt work with glob stars
94
- const tdiIncRegex = /^(config\/)?(({\w*,?)?cmscustom(,?\w*})?\/|\*\/)?\*\*/;
94
+ const tdiIncRegex = /^(config\/)?((\{\w*,?)?cmscustom(,?\w*\})?\/|\*\/)?\*\*/;
95
95
  const tdiPattern = tdiIncRegex.test(transferPattern) ? transferPattern.replace(tdiIncRegex, `${this.deliveryPack ? 'config/' : ''}cmscustom/tdi/**`) : 'nomatch';
96
96
 
97
97
  if (!transferPattern)
@@ -32,7 +32,7 @@ const remote = {
32
32
  do(sshI) {
33
33
  let executing = Math.min(this.queue.length, c.server.ftpConfig.parallel);
34
34
 
35
- if (executing==0) { // close connection and return callback after last exec command
35
+ if (executing===0) { // close connection and return callback after last exec command
36
36
  sshI.dispose();
37
37
  _write();
38
38
  }
@@ -48,14 +48,14 @@ const remote = {
48
48
  else _info(command[1] || command[0], true);
49
49
 
50
50
  executing--;
51
- if (executing==0) this.do(sshI);
51
+ if (executing===0) this.do(sshI);
52
52
  })
53
- .catch(err => {
53
+ .catch(() => {
54
54
  _warn(`Server aborted connection. Retrying.`);
55
55
  this.queue.unshift(command);
56
56
 
57
57
  executing--;
58
- if (executing==0) this.do(sshI);
58
+ if (executing===0) this.do(sshI);
59
59
  });
60
60
  });
61
61
  },
@@ -189,7 +189,7 @@ const transfer = (paths, lrServer) => {
189
189
  remote.process();
190
190
 
191
191
  if (lrServer) { // reload specific resources if files are all image or css, else reload page
192
- const reloadPage = !files.every(f => /.(css|jpe?g|png|gif)$/i.test(f));
192
+ const reloadPage = !files.every(f => /.(?:css|jpe?g|png|gif)$/i.test(f));
193
193
  lrServer.changed({params: {files: reloadPage ? ['page'] : files}});
194
194
  _info(`${reloadPage ? 'Page' : 'Resources'} reloaded\n`);
195
195
  }
@@ -90,7 +90,7 @@ module.exports = function deploy (argv) {
90
90
  if (!path.parse(filepath).base.match(/\.scss/)) {
91
91
  const rp = c.getRemotePath(filepath);
92
92
  const msg = 'Removed: ' + rp.replace(c.server.remotedir, '').white;
93
- if (c.localTransfer) del([rp], {force: true}).then(paths => _info(msg, true));
93
+ if (c.localTransfer) del([rp], {force: true}).then(() => _info(msg, true));
94
94
  else remote.add(`rm -rf "${rp}"`, msg).process();
95
95
  }
96
96
  }
@@ -38,7 +38,7 @@ const cmdExec = command => new Promise((resolve) => {
38
38
  });
39
39
  cp.stderr.setEncoding('utf8');
40
40
  cp.stderr.on('data', data => log(data.replace(/Browserslist: caniuse-lite .*/s, '')));
41
- cp.on('close', code => {
41
+ cp.on('close', () => {
42
42
  _write();
43
43
  resolve();
44
44
  });
@@ -49,15 +49,15 @@ module.exports = {
49
49
 
50
50
  init ([fdt, fontoVersion]) {
51
51
  return new Promise((resolve, reject) => {
52
- _info('Ensure symlink exists:');
53
- fs.symlink(path.join('../../../tdi/fonto/packages-shared'), 'packages-shared', 'dir', err => {
54
- if (err && err.code!='EEXIST') reject(err.code=='EPERM' ? 'Start your console as admin for symlink creation!' : err);
55
- else _write('Done.\n');
56
- resolve();
57
- });
58
- })
59
- .then(() => cmdExec(`${fdt} editor upgrade --version ${fontoVersion} --non-interactive`))
60
- .then(() => [fdt])
52
+ _info('Ensure symlink exists:');
53
+ fs.symlink(path.join('../../../tdi/fonto/packages-shared'), 'packages-shared', 'dir', err => {
54
+ if (err && err.code!='EEXIST') reject(err.code=='EPERM' ? 'Start your console as admin for symlink creation!' : err);
55
+ else _write('Done.\n');
56
+ resolve();
57
+ });
58
+ })
59
+ .then(() => cmdExec(`${fdt} editor upgrade --version ${fontoVersion} --non-interactive`))
60
+ .then(() => [fdt])
61
61
  ;
62
62
  },
63
63
 
@@ -77,7 +77,7 @@ module.exports = {
77
77
 
78
78
  // add dependency for sx-module to each package fonto-manifest.json
79
79
  // add schema packages in fonto.json to config/fonto-manifest.json
80
- Object.entries(rootSchemas).forEach(([xsdPath, obj]) => {
80
+ Object.entries(rootSchemas).forEach(([, obj]) => {
81
81
  const pckPath = 'packages/' + obj.packageName;
82
82
  const pckFontoManifest = fs.readJsonSync(pckPath + '/fonto-manifest.json', {throws: false}) || {dependencies: {}};
83
83
  pckFontoManifest.dependencies['sx-module'] = 'packages/sx-module';
@@ -100,8 +100,8 @@ module.exports = {
100
100
  _info('Copying schema json files to build folders:');
101
101
  // deploy-watch copies schema's to server directly, so update local builds to keep integrity
102
102
  jsonPaths.forEach(jsonPath => {
103
- fs.copySync(jsonPath, 'dev/assets/schemas/' + jsonPath.match(/[^//]*$/)[0]);
104
- fs.copySync(jsonPath, 'dist/assets/schemas/' + jsonPath.match(/[^//]*$/)[0]);
103
+ fs.copySync(jsonPath, 'dev/assets/schemas/' + jsonPath.match(/[^/]*$/)[0]);
104
+ fs.copySync(jsonPath, 'dist/assets/schemas/' + jsonPath.match(/[^/]*$/)[0]);
105
105
  });
106
106
  _write('Done.\n');
107
107
  })
@@ -125,7 +125,7 @@ module.exports = {
125
125
 
126
126
  // execute "fdt elements" for each schema package, ignore default elements, and combine results
127
127
  const schemasPerElement = {};
128
- Object.entries(rootSchemas).forEach(([path, obj]) => {
128
+ Object.entries(rootSchemas).forEach(([, obj]) => {
129
129
  const data = execSync(`${fdt} elements --schema packages/${obj.packageName}/src/assets/schemas/${obj.packageName}.json -C name`, {encoding: 'UTF-8'});
130
130
  const elements = data.replace(/(^.*?Name\*)|(Printed name\*.*$)/gs, '').split(/\s+/);
131
131
  const customElements = [...new Set(elements)].filter(e => e && !ignoreElements.includes(e));
@@ -142,19 +142,19 @@ module.exports = {
142
142
  };
143
143
 
144
144
  return wws(
145
- 'searching',
146
- findElements,
147
- {
148
- fdt,
149
- rootSchemas: fs.readJsonSync('../schema/fonto.json').rootSchemas,
150
- tdiXsdPath: path.join(_paths.repo, _paths.tdi, 'src/config/cmscustom/[customer]/[project]/schema/xsd/')
151
- },
152
- result => {
153
- if (Array.isArray(result)) result.forEach(([e, s]) => _write(e, s, '\n'));
154
- else _write(result, '\n');
155
- }
156
- )
157
- .then(() => [fdt])
145
+ 'searching',
146
+ findElements,
147
+ {
148
+ fdt,
149
+ rootSchemas: fs.readJsonSync('../schema/fonto.json').rootSchemas,
150
+ tdiXsdPath: path.join(_paths.repo, _paths.tdi, 'src/config/cmscustom/[customer]/[project]/schema/xsd/')
151
+ },
152
+ result => {
153
+ if (Array.isArray(result)) result.forEach(([e, s]) => _write(e, s, '\n'));
154
+ else _write(result, '\n');
155
+ }
156
+ )
157
+ .then(() => [fdt])
158
158
  ;
159
159
  },
160
160
 
@@ -174,7 +174,7 @@ module.exports = {
174
174
 
175
175
  // execute "fdt attributes" for each schema package, ignore default attributes, and combine results
176
176
  const schemasPerAttribute = {};
177
- Object.entries(rootSchemas).forEach(([path, obj]) => {
177
+ Object.entries(rootSchemas).forEach(([, obj]) => {
178
178
  const data = execSync(`${fdt} attributes --schema packages/${obj.packageName}/src/assets/schemas/${obj.packageName}.json`, {encoding: 'UTF-8'});
179
179
  const attributes = data.replace(/(^.*?Default value\s+)|(\s+Printed name\*.*$)/gs, '').split(/\n\s+/).map(a => a.split(/\s+/)).map(a =>
180
180
  a[0] + (a[2]=='required' ? ' (required)' : '') + (a[3]=='-' ? ' (no default)' : '')
@@ -193,19 +193,19 @@ module.exports = {
193
193
  };
194
194
 
195
195
  return wws(
196
- 'searching',
197
- findAttributes,
198
- {
199
- fdt,
200
- rootSchemas: fs.readJsonSync('../schema/fonto.json').rootSchemas,
201
- tdiXsdPath: path.join(_paths.repo, _paths.tdi, 'src/config/cmscustom/[customer]/[project]/schema/xsd/')
202
- },
203
- result => {
204
- if (Array.isArray(result)) result.forEach(([e, s]) => _write(e, s, '\n'));
205
- else _write(result, '\n');
206
- }
207
- )
208
- .then(() => [fdt])
196
+ 'searching',
197
+ findAttributes,
198
+ {
199
+ fdt,
200
+ rootSchemas: fs.readJsonSync('../schema/fonto.json').rootSchemas,
201
+ tdiXsdPath: path.join(_paths.repo, _paths.tdi, 'src/config/cmscustom/[customer]/[project]/schema/xsd/')
202
+ },
203
+ result => {
204
+ if (Array.isArray(result)) result.forEach(([e, s]) => _write(e, s, '\n'));
205
+ else _write(result, '\n');
206
+ }
207
+ )
208
+ .then(() => [fdt])
209
209
  ;
210
210
  },
211
211
 
@@ -33,7 +33,7 @@ module.exports = function fonto (argv) {
33
33
  .map(p => ([p.replace('manifest.json', ''), fs.readJsonSync(p).sdkVersion.replace(/Nightlies.*/, 'nightly')])
34
34
  );
35
35
 
36
- if (fontoPaths.length==0) _error('No Fonto instance found.');
36
+ if (fontoPaths.length===0) _error('No Fonto instance found.');
37
37
 
38
38
  let promiseChain = Promise.resolve(); // for sequentially executing commands for each fonto instance
39
39
 
@@ -47,27 +47,27 @@ module.exports = function fonto (argv) {
47
47
 
48
48
  // execute commands sequentially and in correct order
49
49
  return new Promise((resolve) => {
50
- _info('Determining Fonto version:');
51
- let fontoVersionNew = typeof argv.init == 'string' ? argv.init : fontoVersionCurrent;
52
-
53
- if (fontoVersionNew == 'latest') {
54
- const data = execSync(`${fdtCommand(fontoVersionCurrent)} editor versions`, {encoding: 'UTF-8'});
55
- fontoVersionNew = data.match(/\d+\.\d+\.\d+/g).filter(v => allowedFontoVersionRegex.test(v))[0];
56
- }
57
- else if (!allowedFontoVersionRegex.test(fontoVersionNew)) {
58
- _error(`Fonto version ${fontoVersionNew} is not compatible with the current TDI submodule commit!\nExecute: tct fonto --init latest`);
59
- }
60
-
61
- _write(fontoVersionNew+'\n');
62
- return resolve([fdtCommand(fontoVersionNew), fontoVersionNew]);
63
- })
64
- .then(argv.init && commands.init)
65
- .then(argv.schema && commands.schema)
66
- .then(argv.elements && commands.elements)
67
- .then(argv.attributes && commands.attributes)
68
- .then(argv.localize && commands.localize)
69
- .then(argv.build && commands.build)
70
- .then(argv.run && commands.run)
50
+ _info('Determining Fonto version:');
51
+ let fontoVersionNew = typeof argv.init == 'string' ? argv.init : fontoVersionCurrent;
52
+
53
+ if (fontoVersionNew == 'latest') {
54
+ const data = execSync(`${fdtCommand(fontoVersionCurrent)} editor versions`, {encoding: 'UTF-8'});
55
+ fontoVersionNew = data.match(/\d+\.\d+\.\d+/g).filter(v => allowedFontoVersionRegex.test(v))[0];
56
+ }
57
+ else if (!allowedFontoVersionRegex.test(fontoVersionNew)) {
58
+ _error(`Fonto version ${fontoVersionNew} is not compatible with the current TDI submodule commit!\nExecute: tct fonto --init latest`);
59
+ }
60
+
61
+ _write(fontoVersionNew+'\n');
62
+ return resolve([fdtCommand(fontoVersionNew), fontoVersionNew]);
63
+ })
64
+ .then(argv.init && commands.init)
65
+ .then(argv.schema && commands.schema)
66
+ .then(argv.elements && commands.elements)
67
+ .then(argv.attributes && commands.attributes)
68
+ .then(argv.localize && commands.localize)
69
+ .then(argv.build && commands.build)
70
+ .then(argv.run && commands.run)
71
71
  ;
72
72
  });
73
73
  });
@@ -2,7 +2,10 @@ const {execSync, spawn} = require('child_process');
2
2
  const {chdir} = require('process');
3
3
  const inquirer = require('inquirer');
4
4
  const path = require('path');
5
+ const fs = require('fs-extra');
5
6
 
7
+ const execGitCommand = require('../../lib/exec-git-command');
8
+ const getTdiBranch = require('../../lib/get-tdi-branch');
6
9
 
7
10
  const cmdExec = commands => new Promise(resolve => {
8
11
  let promise = new Promise(resolve => resolve());
@@ -21,7 +24,7 @@ const cmdExec = commands => new Promise(resolve => {
21
24
  cp.stdout.on('data', log);
22
25
  cp.stderr.setEncoding('utf8');
23
26
  cp.stderr.on('data', log);
24
- cp.on('close', code => {
27
+ cp.on('close', () => {
25
28
  if (!a[i+1] || a[i+1][1]) _write();
26
29
  resolve();
27
30
  });
@@ -33,6 +36,8 @@ const cmdExec = commands => new Promise(resolve => {
33
36
  });
34
37
  });
35
38
 
39
+ const tdiMigrationFilePath = path.join(_paths.repo, _paths.tdi, 'tct/git/tdiCommitsRequiringMigration.js');
40
+
36
41
  module.exports = function git (argv) {
37
42
 
38
43
  if (argv.init) {
@@ -112,4 +117,93 @@ module.exports = function git (argv) {
112
117
  throw e;
113
118
  });
114
119
  }
120
+
121
+ if (argv.update) {
122
+ const [update, tdiFromDateCustom, tdiToDateCustom] = `${argv.update}`.split(/\s/);
123
+
124
+ if (update === `tdi`) {
125
+
126
+ const tdiBranch = getTdiBranch(argv.branch);
127
+
128
+ if (tdiBranch.from) _info(`Current branch '${tdiBranch.from.name}' will be updated to '${tdiBranch.name}'\nCommon ancestor will be used in selecting TDI commits requiring migration: ${_formatDate(tdiBranch.commonAncestor.date)}`);
129
+ _info(`Branch ${tdiBranch.name} is ${tdiBranch.commitsBehind} commits behind.`);
130
+
131
+ // Set branch in .gitmodules file; This ensures submodule update will follow this branch to the latest commit
132
+ const setBranch = execGitCommand(`submodule set-branch -b ${tdiBranch.name} tangelo-default-implementation`, _paths.repo);
133
+ if (setBranch.error) _error(`Set branch failed: ${setBranch.error}`);
134
+
135
+ if (tdiBranch.commitsBehind > 0 || tdiFromDateCustom) {
136
+ // update submodule
137
+ const updateSubmoduleMsg = execGitCommand(`submodule update --remote`, _paths.repo);
138
+ if (updateSubmoduleMsg.error && !tdiFromDateCustom) _error(`Update submodule failed\n${updateSubmoduleMsg.error}`);
139
+
140
+ if (!tdiFromDateCustom) _info(`TDI submodule updated:\n${updateSubmoduleMsg}`);
141
+
142
+ // tdiMigrationFilePath should exist in latest commits of releases 5.3+; As we updated to the latest version this should work
143
+ if (fs.existsSync(tdiMigrationFilePath)) {
144
+ const migrations = require(tdiMigrationFilePath);
145
+ const fromTdiDate = tdiFromDateCustom ? new Date(tdiFromDateCustom) : (tdiBranch.commonAncestor) ? tdiBranch.commonAncestor.date: _git.commitTdi.local().date;
146
+ const toTdiDate = tdiToDateCustom ? new Date(tdiToDateCustom) : new Date(execGitCommand(`log -1 --format=%cd --date=iso-strict`, path.join(_paths.repo, _paths.tdi), ['date']).date);
147
+
148
+ _info(`TDI commits requiring migration between ${_formatDate(fromTdiDate)} and ${_formatDate(toTdiDate)}`);
149
+ // Filter the migrations that should be applied/considered; Also display older releases migrations
150
+ const migrationsFiltered = migrations
151
+ .filter((m) => m.releases.find((r) => {
152
+ const time = new Date(r.date).getTime();
153
+ return ((tdiBranch.from)
154
+ ? `release/${r.release}` == tdiBranch.name || (tdiBranch.from.name <= `release/${r.release}` && `release/${r.release}` < tdiBranch.name && (fromTdiDate.getTime() < time && time < toTdiDate.getTime()))
155
+ : `release/${r.release}` == tdiBranch.name && (fromTdiDate.getTime() < time && time < toTdiDate.getTime())
156
+ );
157
+ })
158
+ );
159
+
160
+ let relevantMigrationCount = 0;
161
+ // Apply callback for migrations
162
+ migrationsFiltered
163
+ .forEach((m) => {
164
+ const date = _formatDate(new Date(m.releases.filter((r) => tdiBranch.name >= `release/${r.release}`)[0].date));
165
+ relevantMigrationCount += (m.callback(date, relevantMigrationCount+1)) === 1 ? 1 : 0;
166
+ });
167
+
168
+ // Output message based on whether or not migrations are required
169
+ if (migrationsFiltered.length === 0) {
170
+ _write(`-- No commits require migrations --`);
171
+ } else {
172
+ _info(`\n${relevantMigrationCount} relevant migration(s) out of ${migrationsFiltered.length} migration(s) are shown.`);
173
+ _info(`See confluence page 'TDI commits requiring migration' for more detailed information`);
174
+ }
175
+
176
+ } else {
177
+ _error(`Cannot find required files in TDI submodule.`);
178
+ }
179
+ } else {
180
+ _info(`Your TDI submodule is already up to date`);
181
+ }
182
+ } else {
183
+ if (argv.branch) _warn(`Branch argument '${argv.branch}' is ignored for a repository update.`);
184
+
185
+ // Fetch all
186
+ _info(`Fetch all`);
187
+ const cmdFetch = execGitCommand('fetch -pf --all', path.join(_paths.repo));
188
+ if (cmdFetch.error) _warn(`Fetch failed\n${cmdFetch.error}`);
189
+
190
+ // pull repo
191
+ _info(`Pull repository`);
192
+ const cmdPull = execGitCommand(`pull`, _paths.repo);
193
+ if (cmdPull.error) _warn(`Pull failed\n${cmdPull.error}`);
194
+
195
+ _info(`Checked out at commit:`);
196
+ const repoLog = execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, _paths.repo, ['tags', 'hash', 'date']);
197
+ _write(`${_formatDate(repoLog.date)} - ${repoLog.tags} - ${repoLog.hash}`);
198
+
199
+ _info(`Retrieve submodules that belong to this repo-commit`); // update submodule recursively (set to version that belongs to repo)
200
+ _write(execGitCommand(`submodule update --recursive`, path.join(_paths.repo)));
201
+ _info(`Submodule checked out at commit:`);
202
+ const tdiLog = execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, path.join(_paths.repo, _paths.tdi), ['tags', 'hash', 'date']);
203
+ _write(`${_formatDate(tdiLog.date)} - ${tdiLog.tags} - ${tdiLog.hash}`);
204
+ const tdiBranch = getTdiBranch();
205
+
206
+ _info(`\nTDI branch ${tdiBranch.name} is ${tdiBranch.commitsBehind} commits behind.\nUpdate TDI to latest version with 'tct g -u tdi'`);
207
+ }
208
+ }
115
209
  };
@@ -1,10 +1,13 @@
1
- const fs = require('fs-extra');
2
- const globby = require('globby');
3
- const path = require('path');
4
- const {Table} = require('console-table-printer');
5
- const execGitCommand = require('../../lib/exec-git-command');
6
- const c = require('../deploy/config');
7
- const {remote} = require('../deploy/execute');
1
+ const fs = require('fs-extra');
2
+ const globby = require('globby');
3
+ const path = require('path');
4
+ const {Table} = require('console-table-printer');
5
+
6
+ const execGitCommand = require('../../lib/exec-git-command');
7
+ const getTdiBranch = require('../../lib/get-tdi-branch');
8
+ const c = require('../deploy/config');
9
+ const {remote} = require('../deploy/execute');
10
+
8
11
 
9
12
  const getGitInfo = () => {
10
13
  // Version info TDI submodule
@@ -16,30 +19,28 @@ const getGitInfo = () => {
16
19
  });
17
20
 
18
21
  // Fetch all
19
- execGitCommand('fetch -pf --all', path.join(_paths.repo, _paths.tdi));
20
-
21
- // Get branches containing TDI HEAD commit
22
- const releaseBranches = execGitCommand(`branch --all --contains ${_git.commitTdi.local().hash}`, path.join(_paths.repo, _paths.tdi)).match(/release\/[^\s]+/gsm);
23
- if (!releaseBranches) _error('Could not retrieve TDI release branches');
24
- // Get the first possible branch; prefer release/5.1 over release/5.2:
25
- releaseBranches.sort((a, b) => a.name > b.name ? 1 : -1);
26
- const firstBranch = {name: releaseBranches[0]};
22
+ const cmdFetch = execGitCommand('fetch -pf --all', path.join(_paths.repo, _paths.tdi));
23
+ if (cmdFetch.error) _warn(`Fetch failed\n${cmdFetch.error}`);
24
+
25
+ // Set branch name of firstBranch without 'remotes/origin/'
26
+ const tdiBranch = getTdiBranch();
27
+
27
28
  // Get number of commits behind
28
- firstBranch.commitsBehind = execGitCommand(`rev-list HEAD...origin/${firstBranch.name} --count`, path.join(_paths.repo, _paths.tdi));
29
+ tdiBranch.commitsBehind = execGitCommand(`rev-list HEAD...origin/${tdiBranch.name} --count`, path.join(_paths.repo, _paths.tdi));
29
30
 
30
31
  // Create table rows for TDI submodule info
31
32
  gitSubmoduleInfo.addRow({
32
33
  property: 'Commit date',
33
- value: _git.commitTdi.local().date.toLocaleDateString('en-gb', {day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'})
34
+ value: _formatDate(_git.commitTdi.local().date)
34
35
  });
35
- if (firstBranch) {
36
+ if (tdiBranch) {
36
37
  gitSubmoduleInfo.addRow({
37
38
  property: 'Branch',
38
- value: firstBranch.name
39
+ value: tdiBranch.name
39
40
  });
40
41
  gitSubmoduleInfo.addRow({
41
42
  property: 'Commits behind',
42
- value: firstBranch.commitsBehind
43
+ value: tdiBranch.commitsBehind
43
44
  });
44
45
  } else {
45
46
  gitSubmoduleInfo.addRow({
@@ -52,9 +53,8 @@ const getGitInfo = () => {
52
53
  gitSubmoduleInfo.printTable();
53
54
  };
54
55
 
55
- const getFileExtractInfo = (argv) => {
56
+ const getFileExtractInfo = (sorting) => {
56
57
  // version info miscellaneous
57
- const sorting = argv.find.toString().includes('sort-type') ? 'type' : 'project';
58
58
  const projects = new Set;
59
59
  const types = new Set;
60
60
  const versionInfoConfigPath = path.join(_paths.repo, _paths.tdi, 'tct/version/versionInfo.js');
@@ -117,8 +117,8 @@ const getFileExtractInfo = (argv) => {
117
117
  color: 'yellow'
118
118
  });
119
119
  });
120
- } else
121
- if (sorting=='type') {
120
+ }
121
+ else if (sorting=='type') {
122
122
  types.forEach(t => {
123
123
  versionInfo.addRow({ // Add empty row after type
124
124
  path: '',
@@ -129,30 +129,73 @@ const getFileExtractInfo = (argv) => {
129
129
  });
130
130
  }
131
131
  versionInfo.printTable();
132
- } else {
132
+ }
133
+ else {
133
134
  _warn('Version info of miscellaneous items cannot be extracted:\nCannot find required files in TDI submodule. Try updating TDI submodule.');
134
135
  }
135
136
  };
136
137
 
137
- const getServerInfo = (argv) => {
138
+ const getServerInfo = (server) => {
138
139
  // Remote server info
139
140
  // common setup
140
141
  _write();
141
- c.setServer(argv.server);
142
+ c.setServer(server);
142
143
 
143
144
  if (!c.envDev) {
144
145
  _info(`Remote version info for '${c.server.ftpConfig.host}':\n`);
145
146
  remote.add('sudo ~root/scripts/version.sh', '').process();
146
- } else {
147
+ }
148
+ else {
147
149
  _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.');
148
150
  }
149
151
  };
150
152
 
151
- module.exports = function version (argv) {
152
- _info('Version information of this git repository\n');
153
153
 
154
- getGitInfo(argv);
155
- getFileExtractInfo(argv);
156
- getServerInfo(argv);
154
+ module.exports = function info (argv) {
155
+
156
+ if (argv.doctypes) {
157
+ _info('Document type information for this git repository\n');
158
+
159
+ const doctypesInfo = new Table({
160
+ columns: [
161
+ {name: 'id', alignment: 'right'},
162
+ {name: 'name', alignment: 'left'},
163
+ {name: 'paths', alignment: 'left'}
164
+ ],
165
+ });
157
166
 
158
- };
167
+ globby
168
+ .sync(_paths.repo + '/database/config/**/txd_document_types.sql')
169
+ .forEach((p, i, a) => {
170
+ fs.readFileSync(p).toString().match(/select([\s\S]+?)from\s+dual/gmi)
171
+ .forEach((dtRow, i, a) => {
172
+ const ntSqlInsert = fs.readFileSync(p.replace('txd_document_types', 'txd_node_types')).toString().match(/select(.*?)from\s+dual/s)[1];
173
+ const id = dtRow.match(/(\d+) id/)?.[1];
174
+ const name = dtRow.match(/'([^']+)' display_name/)?.[1];
175
+ const dbPath = p.match(/(database\/config\/(:?.*)\/)txd_document_types.sql/i)?.[1];
176
+ const prPath = ntSqlInsert.match(/'([^']+)' xsl_prep_inc/)[1].replace('prepare_xincludes.xsl', '');
177
+
178
+ doctypesInfo.addRows([
179
+ {id, name, paths: 'config/cmscustom/'+ prPath},
180
+ {paths: dbPath}
181
+ ]);
182
+
183
+ if (i!==a.length-1) doctypesInfo.addRow({});
184
+ });
185
+
186
+ if (i!==a.length-1) doctypesInfo.addRow({});
187
+ });
188
+
189
+
190
+ doctypesInfo.printTable();
191
+ }
192
+
193
+ if (argv.versions) {
194
+ _info('Version information for this git repository\n');
195
+
196
+ getGitInfo();
197
+ getFileExtractInfo(argv.versions);
198
+ getServerInfo(argv.server);
199
+ }
200
+
201
+ };
@@ -62,7 +62,7 @@ module.exports = function migrate (argv) {
62
62
  if (p.path_dbconfig.join('/') != p.path_cmscustom.join('/')) projectFolders.push(p.path_cmscustom.join('/'));
63
63
  });
64
64
  // Set filter to chosen active documents
65
- filter = projectFolders.length == 1 ? `**/${projectFolders}/**` : `**/{${projectFolders.join(',')}}/**`;
65
+ filter = projectFolders.length === 1 ? `**/${projectFolders}/**` : `**/{${projectFolders.join(',')}}/**`;
66
66
  }
67
67
 
68
68
  _info(`Filter applied: ${filter}`);
@@ -54,7 +54,7 @@ module.exports = function steps (step, dry, filter) {
54
54
  let filesModCount = 0;
55
55
  for (let i=0; i<20 && r.files[0]; i++) { // execute repeatedly for modified files only (with safety limit of 20)
56
56
  r.files = rif.sync(r).filter(f => f.hasChanged).map(f => f.file);
57
- if (i==0) filesModCount = r.files.length; // save count only after first run (after this only subsets are processed)
57
+ if (i===0) filesModCount = r.files.length; // save count only after first run (after this only subsets are processed)
58
58
  if (dry) break;
59
59
  }
60
60
 
@@ -18,9 +18,9 @@ const checkLog = (logFilePath, remove=true) => {
18
18
  if (fs.existsSync(logFilePath)) {
19
19
  const logFile = fs.readFileSync(logFilePath).toString();
20
20
  // Check for ORA- error messages
21
- if (logFile.match(RegExp(`ORA-`, 'gm'))) {
21
+ if (logFile.match(/ORA-/g)) {
22
22
  // Select each ORA- error message and up to 6 lines before (the lines before may not contain ORA- themselves)
23
- const errors = logFile.match(RegExp(`((?![^\n]*ORA-)[^\n]*\n){0,6}ORA-[^\n\r]*`, `gms`));
23
+ const errors = logFile.match(/(?:(?![^\n]*ORA-)[^\n]*\n){0,6}ORA-[^\n\r]*/gms);
24
24
  _info(`${errors.length} error(s) during SQL script:\n`);
25
25
  // Print each error + lines before:
26
26
  errors.forEach((e, i) => {