@tangelo/tangelo-configuration-toolkit 1.17.0 → 1.18.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
@@ -60,3 +60,35 @@ The `build -x` commands set projects transformation scenarios and masterfiles in
60
60
  - Will try to preserve manually added entries in the transformation scenarios and masterfiles
61
61
  - Will remove non existing masterfiles or masterfiles that start with a '_'
62
62
  - No masterfiles / scenarios will be added if their path match with oXygens hidden directory patterns
63
+
64
+
65
+ ## Debug
66
+
67
+ Save this in the root of your local clone as `tct-dev.cmd`:
68
+
69
+ ```
70
+ @ECHO off
71
+ SETLOCAL
72
+ CALL :find_dp0
73
+
74
+ IF EXIST "%dp0%\node.exe" (
75
+ SET "_prog=%dp0%\node.exe"
76
+ ) ELSE (
77
+ SET "_prog=node"
78
+ SET PATHEXT=%PATHEXT:;.JS;=;%
79
+ )
80
+
81
+ "%_prog%" "%dp0%\index.js" %*
82
+ ENDLOCAL
83
+ EXIT /b %errorlevel%
84
+ :find_dp0
85
+ SET dp0=%~dp0
86
+ EXIT
87
+ ```
88
+
89
+ Then:
90
+
91
+ - open a 'Javascript Debug Terminal' in VSCode
92
+ - jump to a customer folder ( you can use `tan-cust` for this)
93
+ - set a breakpoint
94
+ - type (for instance): `tct-dev d -c`
package/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ global._appStartTime = new Date();
2
+
1
3
  require('./src/lib/style-string-getters');
2
4
 
3
5
  String.prototype.toFws = function(){
@@ -12,6 +14,7 @@ const homedir = require('os').homedir();
12
14
  const path = require('path');
13
15
 
14
16
  const execGitCommand = require('./src/lib/exec-git-command');
17
+ const memoize = require('./src/lib/memoize');
15
18
 
16
19
 
17
20
  const appname = 'tangelo-configuration-toolkit';
@@ -50,6 +53,9 @@ global._formatDate = date =>
50
53
  ;
51
54
 
52
55
 
56
+ global._devmode = !__dirname.includes('node_modules');
57
+
58
+
53
59
  global._packages = {
54
60
  TCT: {name: '@tangelo/tangelo-configuration-toolkit', version: require('./package.json')?.version},
55
61
  FDT: {name: '@fontoxml/fontoxml-development-tools'}
@@ -76,15 +82,6 @@ if (!_appdata.npmPath) {
76
82
  }
77
83
 
78
84
 
79
- if (!fs.existsSync(_paths.appconfig)) { // try to find old appconfig (TCT-162 => remove code block in next version)
80
- fs.ensureDirSync(_paths.apphome);
81
- const oldPathAppconfig = findUp.sync(appname+'-appconfig.json');
82
- if (oldPathAppconfig) {
83
- fs.renameSync(oldPathAppconfig, _paths.appconfig);
84
- _info(`${appname}-appconfig.json moved to ${_paths.apphome}`);
85
- }
86
- }
87
-
88
85
  global._appconfig = { // default appconfig if file not found
89
86
  'sharedConfigPath': path.join(homedir, 'Dropbox (Tangelo Software)/Product Distribution/tooling'),
90
87
  'servers': [],
@@ -105,6 +102,9 @@ catch({code, message}) {
105
102
  }
106
103
 
107
104
 
105
+ global._repoconfig = require('./src/lib/get-repoconfig')(_paths.repo);
106
+
107
+
108
108
  _appconfig.sharedConfigPath = path.resolve(_paths.appconfig, '..', _appconfig.sharedConfigPath || '', appname+'-appconfig.json');
109
109
  _appconfig.shared = fs.readJsonSync(_appconfig.sharedConfigPath, {throws: false}) || {};
110
110
 
@@ -114,49 +114,20 @@ global._git = {
114
114
  if (!_appdata.gitUser) _appdata._update({gitUser: execGitCommand(`config --get user.email`, _paths.repo)});
115
115
  return _appdata.gitUser;
116
116
  },
117
- commitLocal (clearcache) {
118
- if (clearcache) delete this.cache;
119
- this.cache ??= execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, _paths.repo, ['branch', 'hash', 'date']);
120
- return this.cache;
121
- },
122
- commitRemote () {
123
- this.cache ??= execGitCommand('log -1 --format=%cd --date=iso-strict origin/'+_git.commitLocal().branch, _paths.repo, ['date']);
124
- return this.cache;
125
- },
117
+ commitLocal: memoize(() => execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, _paths.repo, ['branch', 'hash', 'date'])),
118
+ commitRemote: memoize(() => execGitCommand('log -1 --format=%cd --date=iso-strict origin/' + _git.commitLocal().branch, _paths.repo, ['date'])),
126
119
  commitTdi: {
127
- local (clearcache) {
128
- if (clearcache) delete this.cache;
129
- this.cache ??= execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, path.join(_paths.repo, _paths.tdi), ['tags', 'hash', 'date']);
130
- return this.cache;
131
- },
132
- after (commitHash) {
133
- this['cache-'+commitHash] ??= execGitCommand(`merge-base --is-ancestor ${commitHash} ${_git.commitTdi.local().hash}`, path.join(_paths.repo, _paths.tdi), null, 0);
134
- return this['cache-'+commitHash];
135
- },
120
+ local: memoize(() => execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, path.join(_paths.repo, _paths.tdi), ['tags', 'hash', 'date'])),
121
+ after: memoize(commitHash => execGitCommand(`merge-base --is-ancestor ${commitHash} ${_git.commitTdi.local().hash}`, path.join(_paths.repo, _paths.tdi), null, 0)),
136
122
  fontoVersions: [ // latest commits first
137
123
  {commitHash: 'e599766', regex: /^7\.1[45]\./},
138
124
  {commitHash: '77b8ea9', regex: /^7\.14\./}, // 7.14.x
139
125
  {commitHash: '8066c44', regex: /^7\.13\./}, // 7.13.x
140
126
  {commitHash: 'a2b2d4e', regex: /^7\.12\./} // 7.12.x
141
- ],
142
- stopUsingRepoconfigFile: '1f12bff' // release/5.1 10-05-2021
127
+ ]
143
128
  }
144
129
  };
145
130
 
146
-
147
- if (_git.commitTdi.after(_git.commitTdi.stopUsingRepoconfigFile)) {
148
- global._repoconfig = require('./src/lib/get-repoconfig')(_paths.repo);
149
- }
150
- else {
151
- try { global._repoconfig = fs.existsSync(_paths.repoconfig) && fs.readJsonSync(_paths.repoconfig) || {}; }
152
- catch ({message}) { _error('Error in '+message); }
153
- global._repoconfig.update = obj => {
154
- global._repoconfig = Object.assign(_repoconfig, obj);
155
- fs.writeJsonSync(_paths.repoconfig, _repoconfig, {spaces: 2});
156
- };
157
- }
158
-
159
-
160
131
  global._tdiSubmoduleExists = () => fs.existsSync(path.join(_paths.repo, _paths.tdi));
161
132
  global._isPre42 = () => fs.existsSync(path.join(_paths.repo, _paths.tdi, 'create_new_project')); // folder changed in 4.2
162
133
  global._isPre51 = () => !fs.existsSync(path.join(_paths.repo, _paths.tdi, 'src')); // folder changed in 5.1 (check new folder because old one could still exist after branch switch)
@@ -175,7 +146,7 @@ global._modulesTdi = {
175
146
  this.depsUpToDate = true;
176
147
  },
177
148
  require(module) {
178
- this.ensureDepsUpToDate();
149
+ if (path.extname(module) !== '.json') this.ensureDepsUpToDate();
179
150
  _info(`Loading ${path.join(_paths.tdi, 'tct', module)}\n`);
180
151
  try {
181
152
  return require(path.join(this.absolutePathTdi, 'tct', module));
@@ -198,7 +169,9 @@ process.on('beforeExit', () => {
198
169
  fs.writeJsonSync(_paths.appdata, _appdata, {spaces: 2});
199
170
  }
200
171
 
172
+ if (_devmode) _perf(_appStartTime); // output execution time in dev mode
173
+
201
174
  });
202
175
 
203
176
 
204
- require('./src/cli.js')();
177
+ require('./src/cli.js')();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangelo/tangelo-configuration-toolkit",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "engines": {
5
5
  "node": ">=14.0.0"
6
6
  },
package/src/cli.js CHANGED
@@ -34,7 +34,6 @@ module.exports = function cli () {
34
34
  init: {alias: 'i', desc: 'Create repository content structure', conflicts: ['p', 's', 'c']},
35
35
  project: {alias: 'p', desc: 'Create project configuration', conflicts: ['i', 's', 'c']},
36
36
  symlinks: {alias: 's', desc: 'Recreate symlinks to TDI', conflicts: ['p', 'i', 'c']},
37
- config: {alias: 'c', desc: 'Create repo-config file', conflicts: ['p', 'i', 's'], hidden: true},
38
37
  oxygen: {alias: 'x', desc: 'Create or update oXygen project file (.xpr)', conflicts: ['p', 'i', 'c']}
39
38
  },
40
39
  handler: require('./modules/build')
@@ -76,8 +75,9 @@ module.exports = function cli () {
76
75
  init: {alias: 'i', desc: 'Initialize repository and add submodule', conflicts: ['r', 'c', 'u']},
77
76
  reset: {alias: 'r', desc: 'Reset repository to last commit', conflicts: ['i', 'c', 'u']},
78
77
  clone: {alias: 'c', desc: 'Clone a client repository and do basic setup', conflicts: ['i', 'r', 'u']},
79
- update: {alias: 'u', desc: 'Update repository or TDI submodule', conflicts: ['i', 'r', 'c']},
80
- branch: {alias: 'b', desc: 'Pass tdi release branch, e.g. "5.4"', conflicts: ['i', 'r', 'c']}
78
+ 'update-repo': {alias: 'ur', desc: 'Update repository', conflicts: ['i', 'r', 'c']},
79
+ 'update-submodule': {alias: 'us', desc: 'Update TDI submodule, optionally pass release branch (without "release/")', conflicts: ['i', 'r', 'c']},
80
+ dates: {alias: 'd', desc: '[hidden] Use i.c.w. update-submodule, pass 2 dates in format dd-mm-yyyy', conflicts: ['i', 'r', 'c'], implies: 'update-submodule', requiresArg: true, array: true, hidden: !_devmode}
81
81
  },
82
82
  handler: require('./modules/git')
83
83
  })
@@ -123,9 +123,9 @@ module.exports = function cli () {
123
123
  ['$0 deploy --copy --server demo', 'Copy files to server "demo"'],
124
124
  ['$0 d -c -s demo', 'Same as above'],
125
125
  ['$0 f -sb && $0 d -c', 'Compile schema and build Fonto, then deploy to default server'],
126
- ['$0 git --update', 'Pull git repository and submodule to latest repository commit'],
127
- ['$0 git --update tdi', 'Update TDI submodule to latest within current TDI branch'],
128
- ['$0 git --update tdi --branch 5.4', 'Update TDI submodule to latest in specified branch']
126
+ ['$0 git --update-repo', 'Pull git repository and submodule to latest repository commit'],
127
+ ['$0 git --update-submodule', 'Update TDI submodule to latest within current TDI branch'],
128
+ ['$0 git --update-submodule 5.4', 'Update TDI submodule to latest in specified branch']
129
129
  ])
130
130
  .check((argv, options) => {
131
131
  const nonDefaultOptions = Object.keys(options.key).filter(o => !Object.keys(options.default).includes(o));
@@ -133,7 +133,7 @@ module.exports = function cli () {
133
133
  else throw new Error('Pass a non-default option');
134
134
  })
135
135
  .strict()
136
- .wrap(100)
136
+ .wrap(120)
137
137
  .parse()
138
138
  ;
139
139
 
@@ -4,11 +4,9 @@ const globby = require('globby');
4
4
 
5
5
  module.exports = function getRepoconfig(repoPath) {
6
6
 
7
- const repoconfig = [];
8
-
9
- globby
7
+ return globby
10
8
  .sync('database/config/**/txd_document_types.sql', {cwd: repoPath, absolute: true})
11
- .forEach(p => {
9
+ .map(p => {
12
10
  const dtSqlInsert = fs.readFileSync(p).toString().match(/select(.*?)from/s)[1];
13
11
  const ntSqlInsert = fs.readFileSync(p.replace('txd_document_types', 'txd_node_types')).toString().match(/select(.*?)from/s)[1];
14
12
 
@@ -21,7 +19,7 @@ module.exports = function getRepoconfig(repoPath) {
21
19
  const dnRegex = [path_cmscustom[0].replace(/[_'"]/g, '.'), path_dbconfig[0].replace(/[_'"]/g, '.')].sort((a, b) => b.length - a.length).join('|');
22
20
  const customer_name = (dtDisplayName.match(RegExp(dnRegex, 'i')) || dtDisplayName.split(/ (.+)/))[0];
23
21
 
24
- repoconfig.push({
22
+ return {
25
23
  customer_name,
26
24
  customer_abbr: ntName.split('_')[0],
27
25
  project_name: dtDisplayName.split(RegExp(customer_name.replace(/(\W)/g, '\\$1')))[1].trim() || '',
@@ -29,9 +27,7 @@ module.exports = function getRepoconfig(repoPath) {
29
27
  path_cmscustom,
30
28
  path_dbconfig,
31
29
  id_range: dtSqlInsert.match(/(\d+)\d{3} id/)[1]
32
- });
30
+ };
33
31
  });
34
32
 
35
- return repoconfig;
36
-
37
33
  };
@@ -2,22 +2,24 @@ const execGitCommand = require('./exec-git-command');
2
2
  const path = require('path');
3
3
 
4
4
  module.exports = function getTdiBranch(toBranchName) {
5
+ const tdiPath = path.join(_paths.repo, _paths.tdi);
5
6
  const tdiBranch = {};
7
+
6
8
  // Fetch all
7
9
  _info(`Fetch TDI submodule`);
8
- const cmdFetch = execGitCommand('fetch -pf --all', path.join(_paths.repo, _paths.tdi));
10
+ const cmdFetch = execGitCommand('fetch -pf --all', tdiPath);
9
11
  if (cmdFetch.error) _warn(`Fetch failed\n${cmdFetch.error}`);
10
12
 
11
13
  let toBranch;
12
14
  if (toBranchName) {
13
15
  // Check if specified branch exists; we will update to this branch
14
16
  toBranch = String(toBranchName).replace(/(?:release\/)?(\d+(?:\.[\dxt]+)*)/, `release/$1`);
15
- const branchExists = execGitCommand(`branch --remote`, path.join(_paths.repo, _paths.tdi)).match(`origin/${toBranch}`);
17
+ const branchExists = execGitCommand(`branch --remote`, tdiPath).match(`origin/${toBranch}`);
16
18
  if (!branchExists) _error(`TDI branch "${toBranch}" does not exist. Note that TCT can only update to a release branch.`);
17
19
  }
18
20
 
19
21
  // Get remote release branches containing TDI HEAD commit
20
- const releaseBranches = execGitCommand(`branch --all --contains ${_git.commitTdi.local().hash}`, path.join(_paths.repo, _paths.tdi)).match(/remotes\/origin\/release\/[^\s]+/gsm);
22
+ const releaseBranches = execGitCommand(`branch --all --contains ${_git.commitTdi.local().hash}`, tdiPath).match(/remotes\/origin\/release\/[^\s]+/gsm);
21
23
  if (!releaseBranches || releaseBranches.error) _error(`Could not retrieve TDI release branches`);
22
24
 
23
25
  // Get the first possible branch; prefer release/5.1 over release/5.2:
@@ -32,13 +34,13 @@ module.exports = function getTdiBranch(toBranchName) {
32
34
  tdiBranch.from = {name: tdiBranch.name};
33
35
  tdiBranch.name = toBranch;
34
36
 
35
- const branchHash = execGitCommand(`rev-parse origin/${toBranch}`, path.join(_paths.repo, _paths.tdi));
36
- const commonAncestorHash = execGitCommand(`merge-base ${_git.commitTdi.local().hash} ${branchHash}`, path.join(_paths.repo, _paths.tdi));
37
- const commonAncestorDate = execGitCommand(`show ${commonAncestorHash} --no-patch --format=%cd --date=iso-strict `, path.join(_paths.repo, _paths.tdi), ['date']).date;
37
+ const branchHash = execGitCommand(`rev-parse origin/${toBranch}`, tdiPath);
38
+ const commonAncestorHash = execGitCommand(`merge-base ${_git.commitTdi.local().hash} ${branchHash}`, tdiPath);
39
+ const commonAncestorDate = execGitCommand(`show ${commonAncestorHash} --no-patch --format=%cd --date=iso-strict `, tdiPath, ['date']).date;
38
40
  tdiBranch.commonAncestor = {date: new Date(commonAncestorDate)};
39
41
  }
40
42
 
41
43
  // Get number of commits behind
42
- tdiBranch.commitsBehind = execGitCommand(`rev-list HEAD...origin/${tdiBranch.name} --count`, path.join(_paths.repo, _paths.tdi));
44
+ tdiBranch.commitsBehind = execGitCommand(`rev-list HEAD...origin/${tdiBranch.name} --count`, tdiPath);
43
45
  return tdiBranch;
44
46
  };
@@ -4,15 +4,15 @@ const es = require('event-stream'), minimatch = require('minimatch'), istextorbi
4
4
 
5
5
  const execReplace = (c, s, r) => Buffer.from(s instanceof RegExp ? String(c).replace(s, r) : String(c).split(s).join(r));
6
6
 
7
- module.exports = (arr) => {
7
+ module.exports = arr => {
8
8
  return es.map((file, callback) => {
9
9
  if(file.contents instanceof Buffer) {
10
- arr.forEach(e => {
10
+ for (const e of arr) {
11
11
  // exec if no glob is passed or if glob matches, and it's a text file
12
12
  if ((!e[2] || minimatch(file.path, e[2])) && istextorbinary.isText(file.path, file)) {
13
13
  file.contents = execReplace(file.contents, e[0], e[1]);
14
14
  }
15
- });
15
+ }
16
16
  }
17
17
  callback(null, file);
18
18
  });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Memoizes a function by caching its results based on the arguments.
3
+ * @param {Function} fn - The function to be memoized.
4
+ * @returns {Function} - The memoized function.
5
+ */
6
+ module.exports = function memoize(fn) {
7
+ let cache = {};
8
+
9
+ /**
10
+ * The memoized function that caches the results of the original function based on its arguments.
11
+ * @param {...*} args - The arguments to be passed to the original function.
12
+ * @returns {*} - The result of the original function.
13
+ */
14
+ const cachedfn = (...args) => {
15
+ const key = JSON.stringify(args);
16
+ cache[key] ??= fn(...args);
17
+ return cache[key];
18
+ };
19
+
20
+ /**
21
+ * Clears the cache of the memoized function.
22
+ */
23
+ cachedfn.clear = () => {
24
+ cache = {};
25
+ };
26
+
27
+ return cachedfn;
28
+ };
@@ -28,7 +28,7 @@ module.exports = function packageUpdateCheck () {
28
28
  const updateMsg = (va) => `| Update ${pkg} to ${va} | ` + `npm i -g ${_packages[pkg].name}`.white;
29
29
  const {versionAvailable} = _appdata[`updateCheck${pkg}`] || {};
30
30
 
31
- if (new Date() - new Date(_appdata[`updateCheck${pkg}`]?.executed || 0) > 1000*3600*24*7) { // check every week
31
+ if (new Date() - new Date(_appdata[`updateCheck${pkg}`]?.executed || 0) > 1000*3600*24*1) { // check every day
32
32
  doUpdateCheck(pkg).then(r => r && _warn(updateMsg(r)));
33
33
  }
34
34
  else if (versionAvailable) {
@@ -3,13 +3,16 @@
3
3
  const styles = {bold: 1, underline: 4, black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37, lblack: 90, lred: 91, lgreen: 92, lyellow: 93, lblue: 94, lmagenta: 95, lcyan: 96, lwhite: 97};
4
4
 
5
5
  for (const name in styles ) {
6
- const style = styles[name];
7
-
8
- String.prototype.__defineGetter__(name, function () {
9
- return `\x1b[${style}m${this}\x1b[0m`;
6
+ Object.defineProperty(String.prototype, name, {
7
+ get () {
8
+ const _this = this.replace(/\x1b\[0m/g, `\x1b[${styles[name]}m`); // replace reset control with color for supporting multiple colors in string
9
+ return `\x1b[${styles[name]}m${_this}\x1b[0m`;
10
+ }
10
11
  });
11
12
  }
12
13
 
13
- String.prototype.__defineGetter__('nostyle', function () {
14
- return this.replace(/[\u001b\u009b][[()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
14
+ Object.defineProperty(String.prototype, 'nostyle', {
15
+ get () {
16
+ return this.replace(/[\u001b\u009b][[()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
17
+ }
15
18
  });
@@ -70,43 +70,5 @@ module.exports = function build (argv) {
70
70
  createSymlinks();
71
71
  }
72
72
 
73
- if (argv.config) {
74
- if (_git.commitTdi.after(_git.commitTdi.stopUsingRepoconfigFile)) _error('This option only works for older repo\'s using a repoconfig file.');
75
-
76
- inquirer
77
- .prompt([{message: 'Be sure paths for projects are [customer]/[project]. Continue?', name: 'confirm', type: 'confirm'}])
78
- .then(({confirm}) => {
79
- if (!confirm) return;
80
-
81
- const customer = {}, projects = [];
82
-
83
- globby
84
- .sync('database/config/**/txd_document_types.sql')
85
- .forEach(p => {
86
- const dtSqlInsert = fs.readFileSync(p).toString().match(/select(.*?)from/s)[1];
87
- const ntSqlInsert = fs.readFileSync(p.replace('txd_document_types', 'txd_node_types')).toString().match(/select(.*?)from/s)[1];
88
-
89
- const dtDisplayName = dtSqlInsert.match(/'([^']+)' display_name/)[1];
90
- const ntName = ntSqlInsert.match(/'([^']+)' name/)[1];
91
- const xpiDir = ntSqlInsert.match(/'([^']+)' xsl_prep_inc/)[1];
92
-
93
- customer.name = dtDisplayName.split(/ (.+)/)[0];
94
- customer.abbr = ntName.split('_')[0];
95
- customer.dirname = xpiDir.split('/')[0];
96
-
97
- projects.push({
98
- name: dtDisplayName.split(/ (.+)/)[1],
99
- abbr: ntName.split('_')[1],
100
- dirname: xpiDir.split('/')[1],
101
- idrange: dtSqlInsert.match(/(\d+)\d{3} id/)[1]
102
- });
103
- });
104
-
105
- _repoconfig.update({customer, projects, applink: 'None'});
106
-
107
- _write(`\nFile '${_paths.repoconfig}' has been created.`);
108
- });
109
- }
110
-
111
73
  if (argv.oxygen) oxygen(argv.oxygen);
112
74
  };
@@ -3,141 +3,136 @@ const objMerge = require('object-assign-deep');
3
3
  const path = require('path');
4
4
 
5
5
 
6
- module.exports = {
7
-
8
- getRemotePath (...arr) {
9
- const fp = path.join(this.server.remotedir, ...arr);
10
- return this.server.remotedir.match(/:/) ? fp : fp.toFws();
11
- },
12
-
13
- setServer (server) {
14
- if (!server) _error('No server was passed.');
15
-
16
- let serverConfigs = [];
17
-
18
- // find server in local config
19
- if (_appconfig.servers)
20
- serverConfigs = _appconfig.servers.filter(obj =>
21
- obj.name == server || (obj.domains && obj.domains.find(d => d.startsWith(server)))
22
- );
23
-
24
- // if nothing found, find server in shared config
25
- if (!serverConfigs[0] && _appconfig.shared.servers)
26
- serverConfigs = _appconfig.shared.servers.filter(obj =>
27
- obj.name == server || (obj.domains && obj.domains.find(d => d.startsWith(server)))
28
- );
29
-
30
- // complement configs with defaults and filter domains
31
- serverConfigs = serverConfigs.map(obj =>
32
- objMerge(
33
- {config: {}}, // make sure key is present
34
- _appconfig.shared.serverDefaults, // override with shared defaults
35
- _appconfig.serverDefaults, obj, // override with local defaults
36
- {domains: (obj.domains||[]).filter(d => obj.name == server || d.startsWith(server))} // make sure key is present and filter for domain (if not matched for name)
37
- )
38
- );
6
+ function getRemotePath (...arr) {
7
+ const fp = path.join(this.server.remotedir, ...arr);
8
+ return this.server.remotedir.includes(':') ? fp : fp.toFws();
9
+ }
10
+
11
+
12
+ function setServer (searchString) {
13
+ if (!searchString) _error('No server was passed.');
14
+
15
+ const stringsToArrays = stringOrArray => [stringOrArray].flat();
16
+ const filterDomains = domain => domain[0].startsWith(searchString) || domain.slice(1).includes(searchString);
17
+ const filterServerConfigs = (arr, cfg) => {
18
+ if (cfg.domains) cfg.domains = cfg.domains.map(stringsToArrays).filter(filterDomains);
19
+ if (cfg.name === searchString || cfg.domains?.[0]) arr.push(cfg);
20
+ return arr;
21
+ };
22
+
23
+ // find server in local config and filter domains
24
+ let serverConfigs = _appconfig.servers?.reduce(filterServerConfigs, []);
25
+ // if nothing found, find server in shared config and filter domains
26
+ if (!serverConfigs?.[0]) serverConfigs = _appconfig.shared.servers?.reduce(filterServerConfigs, []);
27
+
28
+ // abort if no config is found, or if multiple configs or domains are found for server
29
+ if (!serverConfigs?.[0]) _error(`No config found for "${searchString}".`);
30
+ if (serverConfigs.length > 1 || serverConfigs[0].domains?.length > 1) {
31
+ const matches = serverConfigs.map(obj => obj.name === searchString ? obj.name : obj.domains);
32
+ const matchesFlatten = matches.flat().map(stringsToArrays).map(m => m[0] + (m[1] ? ` [${m.slice(1).join(', ')}]` : '')).join(', ');
33
+ _error(`Passed server name "${searchString}" gives multiple results:\n${matchesFlatten}`);
34
+ }
39
35
 
40
- // abort if no config is found, or multiple configs, or multiple domains, are found for server
41
- if (!serverConfigs[0]) _error(`No config found for "${server}".`);
42
- if (serverConfigs.length > 1 || serverConfigs[0].domains.length > 1) {
43
- const matches = serverConfigs.map(obj => obj.name == server ? obj.name : obj.domains.filter(d => d.startsWith(server)));
44
- _error(`Passed server name "${server}" gives multiple results:\n${matches.flat().join(', ')}`);
45
- }
46
-
47
- // compose final server config in usable format
48
- const [serverConfig] = serverConfigs;
49
- this.server = {
50
- remotedir: serverConfig.config.remotedir,
51
- ftpConfig: {
52
- host: serverConfig.domains[0],
53
- port: serverConfig.config.port,
54
- parallel: serverConfig.config.parallel,
55
- username: serverConfig.config.username,
56
- agent: process.platform=='win32' ? 'pageant' : process.env.SSH_AUTH_SOCK,
57
- agentForward: process.platform!='win32',
58
- readyTimeout: 15000,
59
- retries: 1
60
- }
36
+ // complement serverconfig with defaults
37
+ const serverConfig = objMerge(
38
+ {config: {}, domains: [[]]}, // make sure keys are present
39
+ _appconfig.shared.serverDefaults, // override with shared defaults
40
+ _appconfig.serverDefaults, // override with local defaults
41
+ serverConfigs[0]
42
+ );
43
+
44
+ // compose final server config in usable format
45
+ const {config, domains: [[host]], mode} = serverConfig;
46
+
47
+ this.server = {remotedir: config.remotedir};
48
+
49
+ if (host) {
50
+ const {parallel, port, username} = config;
51
+ if (!(port && parallel && username)) _error('Config is incomplete!');
52
+ this.server.ftpConfig = {
53
+ host, port, parallel, username,
54
+ agent: process.platform === 'win32' ? 'pageant' : process.env.SSH_AUTH_SOCK,
55
+ agentForward: process.platform !== 'win32',
56
+ readyTimeout: 15000,
57
+ retries: 1
61
58
  };
59
+ }
60
+
61
+ if (mode === 'delivery-pack') {
62
+ const dateStr = new Date().toISOString().replace(/(.*)T(\d+):(\d+):(\d+).*/g, '$1_$2$3$4');
63
+ this.deliveryPackName = `tangelo-config-delivery-pack_${_paths.repo.split(/[\\/]/).pop()}_${dateStr}`;
64
+ this.server.remotedir += '/' + this.deliveryPackName; // output to temp folder
65
+ this.deliveryPack = true;
66
+ }
67
+
68
+ this.envDev = !host && !this.deliveryPack || /\.dev\.tangelo\.nl$/.test(host);
69
+ }
70
+
71
+
72
+ function prepareForCopy (filter) {
73
+ const {remotedir, ftpConfig} = this.server;
74
+ if (!path.isAbsolute(remotedir)) _error('"remotedir" must contain an absolute path!');
75
+
76
+ this.giPatterns = !fs.existsSync('.gitignore') ? [] :
77
+ fs.readFileSync('.gitignore').toString()
78
+ .trim()
79
+ .replace(/^#.+/gm, '') // remove comments
80
+ .split(/\s+/) // split on lines
81
+ .filter(p => !p.match(/(^database\/)|(\/tdi)|(\/fonto)/)) // filter paths that should be copied
82
+ .map(p => '!' + (p.match(/^config/) ? p : '**/' + p.split('**/').pop()) + (p.match(/\/$/) ? '**' : '')) // negate expressions, remove base dir, suffix folders with **
83
+ ;
84
+
85
+ let transferPattern = path.join(_paths.apply, filter).toFws();
86
+
87
+ // test if 'cmscustom/tdi' would be included, then add specifically, because following symlinks doesnt work with glob stars
88
+ const tdiIncRegex = /^(config\/)?((\{\w*,?)?cmscustom(,?\w*\})?\/|\*\/)?\*\*/;
89
+ const tdiPattern = tdiIncRegex.test(transferPattern) ? transferPattern.replace(tdiIncRegex, `${this.deliveryPack ? 'config/' : ''}cmscustom/tdi/**`) : 'nomatch';
90
+
91
+ if (!transferPattern)
92
+ _error('Invalid glob expression passed!');
93
+ else if (this.deliveryPack && transferPattern.startsWith('*'))
94
+ transferPattern = `{config,database,${_paths.tdi}/src/database/tdi}/${transferPattern}`;
95
+ else if (!this.deliveryPack) {
96
+ // for normal deployment to server, set config-dir so no other files in repo are unintentionally transferred
97
+ transferPattern = transferPattern.replace(/^config\/|^\//, '');
98
+ try { process.chdir('config'); }
99
+ catch(e) { _error('No config dir present!'); }
100
+ }
101
+
102
+ this.transferPatterns = [transferPattern, tdiPattern, ...this.giPatterns, '!**/*.crdownload']; // ignore patterns must come last
103
+
104
+ if (this.deliveryPack && transferPattern.includes(_paths.tdi)) this.transferPatterns.push(`${_paths.tdi}/src/database/create*.sql`); // create(_exists).sql is called by tdi install
105
+
106
+ // add time parameters to all xopus requests to overcome cache
107
+ const ts = new Date().toISOString().replace(/[-:]/g, '').substring(0, 15);
108
+ this.replaceStrings = [
109
+ [/((xsd|xsl|src|iconsrc)="[^"?]+)"/g, `$1?_=${ts}"`, '**/xopus/xml/*'],
110
+ [/(schemaLocation="[^"?]+)"/g, `$1?_=${ts}"`, '**/xopus/xsd/*'],
111
+ [/(href="[^"?]+.xsl)"/g, `$1?_=${ts}"`, '**/xopus/xsl/*']
112
+ ];
113
+ // depending on env, enable debug mode and change track functionality
114
+ if (this.envDev) {
115
+ this.transferPatterns.push('!ldap.xml');
116
+ this.replaceStrings.push(
117
+ ['debug.on=false', 'debug.on=true', '**/util.js'],
118
+ ['debugMode>false', 'debugMode>true', '**/xopus/xml/*'],
119
+ ['allowAccept>false', 'allowAccept>true', '**/xopus/xml/*'],
120
+ ['allowReject>false', 'allowReject>true', '**/xopus/xml/*'],
121
+ [/(id="generateSampleText" available=")false/, '$1true', '**/xopus/xml/*']
122
+ );
123
+ }
124
+ else this.replaceStrings.push(['debugMode>true', 'debugMode>false', '**/xopus/xml/*']);
125
+
126
+ _info(`Branch: ${_git.commitLocal().branch}`);
127
+ _info(`Source: ${path.join(process.cwd(), transferPattern)}`);
128
+ _info(`Server: ${ftpConfig ? `${ftpConfig.host}:${ftpConfig.port}` : 'local'}`);
129
+ _info(`Target: ${this.getRemotePath(transferPattern)}`);
130
+ _write();
62
131
 
63
- const {ftpConfig} = this.server;
64
-
65
- if (ftpConfig.host && !(ftpConfig.port && ftpConfig.parallel && ftpConfig.username)) _error('Config is incomplete!');
66
-
67
- this.localTransfer = !ftpConfig.host;
68
- this.deliveryPack = serverConfig.mode == 'delivery-pack';
69
- this.envDev = this.localTransfer && !this.deliveryPack || /\.dev\.tangelo\.nl$/.test(ftpConfig.host);
70
-
71
- if (this.deliveryPack) {
72
- const dateStr = new Date().toISOString().replace(/(.*)T(\d+):(\d+):(\d+).*/g, '$1_$2$3$4');
73
- this.deliveryPackName = `tangelo-config-delivery-pack_${_paths.repo.split(/[\\/]/).pop()}_${dateStr}`;
74
- this.server.remotedir += '/' + this.deliveryPackName; // output to temp folder
75
- }
76
- },
77
-
78
- prepareForCopy (filter) {
79
- const {remotedir, ftpConfig} = this.server;
80
- if (!path.isAbsolute(remotedir)) _error('"remotedir" must contain an absolute path!');
81
-
82
- this.giPatterns = !fs.existsSync('.gitignore') ? [] :
83
- fs.readFileSync('.gitignore').toString()
84
- .trim()
85
- .replace(/^#.+/gm, '') // remove comments
86
- .split(/\s+/) // split on lines
87
- .filter(p => !p.match(/(^database\/)|(\/tdi)|(\/fonto)/)) // filter paths that should be copied
88
- .map(p => '!' + (p.match(/^config/) ? p : '**/' + p.split('**/').pop()) + (p.match(/\/$/) ? '**' : '')) // negate expressions, remove base dir, suffix folders with **
89
- ;
90
-
91
- let transferPattern = path.join(_paths.apply, filter).toFws();
92
-
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*\})?\/|\*\/)?\*\*/;
95
- const tdiPattern = tdiIncRegex.test(transferPattern) ? transferPattern.replace(tdiIncRegex, `${this.deliveryPack ? 'config/' : ''}cmscustom/tdi/**`) : 'nomatch';
96
-
97
- if (!transferPattern)
98
- _error('Invalid glob expression passed!');
99
- else if (this.deliveryPack && transferPattern.startsWith('*'))
100
- transferPattern = `{config,database,${_paths.tdi}/src/database/tdi}/${transferPattern}`;
101
- else if (!this.deliveryPack) {
102
- // for normal deployment to server, set config-dir so no other files in repo are unintentionally transferred
103
- transferPattern = transferPattern.replace(/^config\/|^\//, '');
104
- try { process.chdir('config'); }
105
- catch(e) { _error('No config dir present!'); }
106
- }
107
-
108
- this.transferPatterns = [transferPattern, tdiPattern, ...this.giPatterns, '!**/*.crdownload']; // ignore patterns must come last
109
-
110
- if (this.deliveryPack && transferPattern.includes(_paths.tdi)) this.transferPatterns.push(`${_paths.tdi}/src/database/create*.sql`); // create(_exists).sql is called by tdi install
111
-
112
- // add time parameters to all xopus requests to overcome cache
113
- const ts = new Date().toISOString().replace(/[-:]/g, '').substring(0, 15);
114
- this.replaceStrings = [
115
- [/((xsd|xsl|src|iconsrc)="[^"?]+)"/g, `$1?_=${ts}"`, '**/xopus/xml/*'],
116
- [/(schemaLocation="[^"?]+)"/g, `$1?_=${ts}"`, '**/xopus/xsd/*'],
117
- [/(href="[^"?]+.xsl)"/g, `$1?_=${ts}"`, '**/xopus/xsl/*']
118
- ];
119
- // depending on env, enable debug mode and change track functionality
120
- if (this.envDev) {
121
- this.transferPatterns.push('!ldap.xml');
122
- this.replaceStrings.push(
123
- ['debug.on=false', 'debug.on=true', '**/util.js'],
124
- ['debugMode>false', 'debugMode>true', '**/xopus/xml/*'],
125
- ['allowAccept>false', 'allowAccept>true', '**/xopus/xml/*'],
126
- ['allowReject>false', 'allowReject>true', '**/xopus/xml/*'],
127
- [/(id="generateSampleText" available=")false/, '$1true', '**/xopus/xml/*']
128
- );
129
- }
130
- else this.replaceStrings.push(['debugMode>true', 'debugMode>false', '**/xopus/xml/*']);
131
-
132
- _info(`Branch: ${_git.commitLocal().branch}`);
133
- _info(`Source: ${path.join(process.cwd(), transferPattern)}`);
134
- _info(`Server: ${ftpConfig.host ? `${ftpConfig.host}:${ftpConfig.port}` : 'local'}`);
135
- _info(`Target: ${this.getRemotePath(transferPattern)}`);
136
- _write();
137
-
138
- if (_git.commitLocal().date < _git.commitRemote().date) {
139
- _warn(`You're not deploying from the most recent git commit!\n`);
140
- }
132
+ if (_git.commitLocal().date < _git.commitRemote().date) {
133
+ _warn(`You're not deploying from the most recent git commit!\n`);
141
134
  }
135
+ }
136
+
142
137
 
143
- };
138
+ module.exports = {getRemotePath, setServer, prepareForCopy};
@@ -149,7 +149,7 @@ const transfer = (paths, lrServer) => {
149
149
  });
150
150
 
151
151
  // disable file logging for ftp (gulp-sftp does it already)
152
- g_print.setLogFunction((filepath) => c.localTransfer ? _write(filepath.nostyle) : null);
152
+ g_print.setLogFunction(filepath => c.server.ftpConfig ? null : _write(filepath.nostyle));
153
153
 
154
154
  _info('Start transferring', true);
155
155
 
@@ -181,7 +181,7 @@ const transfer = (paths, lrServer) => {
181
181
  cb(null, file);
182
182
  }))
183
183
  .pipe(g_print.default())
184
- .pipe(c.localTransfer ? gulp.dest(c.server.remotedir) : g_sftp(c.server.ftpConfig, c.server.remotedir))
184
+ .pipe(c.server.ftpConfig ? g_sftp(c.server.ftpConfig, c.server.remotedir) : gulp.dest(c.server.remotedir))
185
185
  .on('end', () => {
186
186
  _info('Finished transferring\n', true);
187
187
  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 ')}`);
@@ -16,8 +16,8 @@ module.exports = function deploy (argv) {
16
16
  c.setServer(argv.server);
17
17
  c.prepareForCopy(argv.filter);
18
18
 
19
- if (!c.localTransfer) {
20
- c.server.ftpConfig.eventPut = (file) => {
19
+ if (c.server.ftpConfig) {
20
+ c.server.ftpConfig.eventPut = file => {
21
21
  _write(file.destination.replace(c.server.remotedir, ''));
22
22
  if (path.extname(file.destination)=='.sh')
23
23
  remote.add('chmod 755 '+file.destination, 'Permissions set: '+file.destination);
@@ -64,7 +64,7 @@ module.exports = function deploy (argv) {
64
64
  };
65
65
 
66
66
  // check connection
67
- if (!c.localTransfer) {
67
+ if (c.server.ftpConfig) {
68
68
  const sftp = new sftpClient();
69
69
  sftp.connect(c.server.ftpConfig).then(() => sftp.end()).catch(err => _error(`Could not connect to server${err ? ': '+err.message : ''}`));
70
70
  }
@@ -77,7 +77,7 @@ module.exports = function deploy (argv) {
77
77
  // the "dist" folder isn't watched properly: it does not detect "assets" anymore after building once
78
78
  !/cmscustom.+fonto[\\/]/.test(filepath) ||
79
79
  /fonto[\\/]dist[\\/]/.test(filepath) ||
80
- (/fonto[\\/]dev[\\/]/.test(filepath) && c.envDev) ||
80
+ (/fonto[\\/]dev[\\/]/.test(filepath)) ||
81
81
  /fonto[\\/]packages[\\/]sx-shell-.*?[\\/]assets[\\/]schemas[\\/]/.test(filepath)
82
82
  )
83
83
  ) {
@@ -90,8 +90,8 @@ 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(() => _info(msg, true));
94
- else remote.add(`rm -rf "${rp}"`, msg).process();
93
+ if (c.server.ftpConfig) remote.add(`rm -rf "${rp}"`, msg).process();
94
+ else del([rp], {force: true}).then(() => _info(msg, true));
95
95
  }
96
96
  }
97
97
  });
@@ -235,8 +235,15 @@ module.exports = {
235
235
 
236
236
  return cmdExec(`${fdt} localization extract ${templateFile} --paths ` + packagesDirs)
237
237
  .then(() => cmdExec(`${fdt} localization update ${messagesFile} ${templateFile}`))
238
- .then(() => fs.readFile(messagesFile, {encoding: 'UTF-8'}))
239
- .then(data => fs.outputFile(messagesFile, data.replace(/src\/operations-_/g, 'src/_'))) // remove "operations-" prefix from meta[.file]
238
+ .then(() => fs.readJson(messagesFile))
239
+ .then(data => // compress meta object into string array "refs" and remove "operations-" prefix from paths
240
+ data.map(msg => {
241
+ msg.refs = msg.meta.map(r => Object.values(r).join(':').replace(/src\/operations-_/, 'src/_'));
242
+ delete msg.meta;
243
+ return msg;
244
+ })
245
+ )
246
+ .then(data => fs.outputJson(messagesFile, data, {spaces: 2}))
240
247
  .then(() => {
241
248
  _info('Cleanup temp files:');
242
249
  return del([templateFile, ...attrCfgTmpFiles]);
@@ -272,7 +279,7 @@ module.exports = {
272
279
  });
273
280
  _write('Done.\n');
274
281
  })
275
- .then(() => cmdExec(`${fdt} editor run --write-to-disk`))
282
+ .then(() => cmdExec(`${fdt} editor run --port 8888 --write-to-disk`))
276
283
  ;
277
284
  }
278
285
 
@@ -53,7 +53,7 @@ module.exports = function fonto (argv) {
53
53
  fontoVersionNew = data.match(/\d+\.\d+\.\d+/g).filter(v => allowedFontoVersionRegex.test(v))[0];
54
54
  }
55
55
  else if (!allowedFontoVersionRegex.test(fontoVersionNew)) {
56
- _error(`Fonto version ${fontoVersionNew} is not compatible with the current TDI submodule commit!\nExecute: tct fonto --init latest`);
56
+ _error(`Fonto version ${fontoVersionNew} is not compatible with the current TDI submodule commit!\nExecute: ${'tct fonto --init latest'.cyan}`);
57
57
  }
58
58
 
59
59
  _write(fontoVersionNew+'\n');
@@ -1,4 +1,6 @@
1
1
  const {execSync, spawn} = require('child_process');
2
+ const fs = require('fs-extra');
3
+ const globby = require('globby');
2
4
  const inquirer = require('inquirer');
3
5
  const path = require('path');
4
6
 
@@ -36,172 +38,189 @@ const cmdExec = commands => new Promise(resolve => {
36
38
  });
37
39
 
38
40
 
39
- module.exports = function git (argv) {
41
+ function init () {
42
+ const remoteTdiUrl = `${_paths.gitremote}/${_paths.tdi}.git`;
43
+ let branches;
44
+
45
+ try { branches = execSync('git ls-remote --heads ' + remoteTdiUrl, {encoding: 'UTF-8'}); }
46
+ catch (e) { _error(' '); } // execSync already prints an error to the console
47
+
48
+ branches = branches
49
+ .match(/refs\/heads\/\S+/g)
50
+ .map(s => s.replace('refs/heads/', ''))
51
+ .sort((a, b) => {
52
+ const aRelease = /^release\//.test(a), bRelease = /^release\//.test(b);
53
+ if (aRelease && bRelease) return a > b ? -1 : 1;
54
+ if (aRelease) return -1;
55
+ if (bRelease) return 1;
56
+ return a > b ? 1 : -1;
57
+ })
58
+ ;
59
+ inquirer
60
+ .prompt([{
61
+ message: 'Choose a branch for TDI submodule: ', name: 'branch', type: 'list',
62
+ choices: branches, pageSize: 3, loop: false
63
+ }])
64
+ .then(a => {
65
+ _write();
66
+ cmdExec([
67
+ ['git init', 'Initialize repo:'],
68
+ [`git remote add origin ${_paths.gitremote.replace(/\//, ':')}/${path.basename(process.cwd()).replace(/\W/, '-')}.git`],
69
+ [`git submodule add -b ${a.branch} ${remoteTdiUrl}`, 'Add TDI submodule:'],
70
+ ['git commit -m "added tdi submodule"', 'Commit:']
71
+ ])
72
+ .then(() => {
73
+ _info(`Next steps:
74
+ 1. Go to https://bitbucket.org/repo/create?workspace=tangelosoftware and create a repository named "${path.basename(process.cwd()).replace(/\W/, '-')}", with options for including README/.gitignore disabled.
75
+ 2. Open this folder in Sourcetree (ctrl+O), and push master branch to origin.
76
+ `.replace(/^\s{8}/gm, ''));
77
+ });
78
+ });
79
+ }
80
+
81
+
82
+ function reset () {
83
+ _info('Reset and clean repo:');
84
+ cmdExec([
85
+ ['git reset --hard'],
86
+ ['git clean -f -d']
87
+ ]);
88
+ }
89
+
90
+
91
+ function clone ({clone: repoName}) {
92
+
93
+ if (typeof repoName !== 'string') _error('Pass a repository name!');
94
+
95
+ _info('Clone a client repository and do basic project initialization');
96
+ cmdExec([
97
+ // Clone repository
98
+ ['git clone git@bitbucket.org:tangelosoftware/' + repoName + '.git']
99
+ ])
100
+ .then(() => {
101
+ // Change directory for the next commands, it should have the same name as the clone argument from commandline at this point
102
+ process.chdir('./' + repoName);
103
+ cmdExec([
104
+ // Retrieve TDI submodule for this client
105
+ ['git submodule update --init', 'Fetch submodules such as TDI'],
106
+ // Create symlinks for TDI
107
+ ['tct b -s', 'Create TDI symlinks'],
108
+ // Create Oxygen project file and set name equal to repo name
109
+ [`tct b -x ${repoName}`, 'Create Oxygen project file'],
110
+ ]);
111
+ })
112
+ .catch(e => {throw e;})
113
+ ;
114
+ }
115
+
116
+
117
+ /**
118
+ * Updates the repository to the latest commit.
119
+ * Checks the version of the Fonto instances and warns if there are changes.
120
+ */
121
+ function updateRepo () {
122
+ const readFontoVersionFromFile = relPath => fs.readJSONSync(path.join(_paths.repo, 'config/cmscustom', relPath)).sdkVersion;
123
+ const fontoLocations = globby.sync('*/*/fonto/manifest.json', {cwd: path.join(_paths.repo, 'config/cmscustom')});
124
+ const fontoVersions = {};
125
+ fontoLocations.forEach(p => {fontoVersions[p] = readFontoVersionFromFile(p);});
126
+
127
+ _info(`Fetch all`);
128
+ const cmdFetch = execGitCommand('fetch -pf --all', _paths.repo);
129
+ if (cmdFetch.error) _warn(`Fetch failed\n${cmdFetch.error}`);
130
+
131
+ _info(`Pull repository`);
132
+ const cmdPull = execGitCommand(`pull`, _paths.repo);
133
+ if (cmdPull.error) _warn(`Pull failed\n${cmdPull.error}`);
134
+
135
+ _info(`Checked out at commit:`);
136
+ const repoLog = _git.commitLocal();
137
+ _write([_formatDate(repoLog.date), repoLog.tags, repoLog.hash].filter(v=>v).join(' - '));
138
+
139
+ if (fontoLocations.find(p => fontoVersions[p] !== readFontoVersionFromFile(p))) _warn(`\nChanges in Fonto instance versions have been pulled. Therefore you must execute ${'tct fonto --init'.cyan} before building and deploying Fonto.`);
140
+
141
+ _info(`\nRetrieve submodules that belong to this repo-commit`); // update submodule recursively (set to version that belongs to repo)
142
+ _write(execGitCommand(`submodule update --recursive`, _paths.repo));
143
+ _info(`Submodule checked out at commit:`);
144
+ const tdiLog = _git.commitTdi.local();
145
+ _write([_formatDate(tdiLog.date), tdiLog.tags, tdiLog.hash].filter(v=>v).join(' - '));
146
+
147
+ const tdiBranch = getTdiBranch();
148
+ _info(`\nTDI branch ${tdiBranch.name} is ${tdiBranch.commitsBehind} commits behind.\nUpdate TDI submodule to latest version with ${'tct git --update-submodule'.cyan}`);
149
+ }
150
+
151
+
152
+ /**
153
+ * Updates a submodule in a Git repository. Allows for custom date ranges (for testing) and branch upgrades, and applies migrations to the repository if necessary.
154
+ *
155
+ * @param {string[]} options.dates - An array of two strings representing the start and end dates for the custom date range.
156
+ * @param {string} options.updateSubmodule - The target branch name for the submodule update.
157
+ */
158
+ function updateSubmodule({dates = [], updateSubmodule: toBranchName}) {
159
+ const [dateCustomFrom, dateCustomTo] = dates;
160
+ const branch = getTdiBranch(typeof toBranchName !== 'boolean' && toBranchName);
161
+ const branchUpgrade = branch.from ? branch.from.name < branch.name : false;
162
+
163
+ if (branchUpgrade) _info(`Current branch '${branch.from.name}' will be updated to '${branch.name}'\nCommon ancestor will be used in selecting TDI commits requiring migration: ${_formatDate(branch.commonAncestor.date)}`);
164
+ _info(`Branch ${branch.name} is ${branch.commitsBehind} commits behind.\n`);
165
+
166
+ // Set branch in .gitmodules file; This ensures submodule update will follow this branch to the latest commit
167
+ const setBranch = execGitCommand(`submodule set-branch -b ${branch.name} tangelo-default-implementation`, _paths.repo);
168
+ if (setBranch.error) _error(`Set branch failed: ${setBranch.error}`);
169
+
170
+ if (branch.commitsBehind > 0 || dateCustomFrom) {
171
+ const dateBeforeUpdate = _git.commitTdi.local().date;
172
+
173
+ // update submodule
174
+ const updateSubmoduleMsg = execGitCommand(`submodule update --remote`, _paths.repo);
175
+ if (updateSubmoduleMsg.error) _error(`Update submodule failed\n${updateSubmoduleMsg.error}`);
176
+ if (updateSubmoduleMsg) _info(`TDI submodule updated:\n${updateSubmoduleMsg}\n`);
177
+
178
+ // tdiMigrationFilePath should exist in latest commits of releases 5.3+; As we updated to the latest version this should work
179
+ const migrations = _modulesTdi.require('git/tdiCommitsRequiringMigration.js');
180
+ const fromDate = dateCustomFrom ? new Date(dateCustomFrom) : branch.commonAncestor?.date ?? dateBeforeUpdate;
181
+ const toDate = dateCustomTo ? new Date(dateCustomTo) : new Date();
182
+
183
+ _info(`TDI commits requiring migration between ${_formatDate(fromDate)} and ${_formatDate(toDate)}`);
184
+ // Filter the migrations that should be applied/considered; Also display older releases migrations
185
+ // For a branch upgrade, all the migrations need to be executed from the beginning of the target branch, not just within the given date range
186
+ const migrationsFiltered = migrations.filter(m => m.releases.find(r => {
187
+ const withinDateRange = fromDate < new Date(r.date) && new Date(r.date) < toDate;
188
+ const isTargetBranch = `release/${r.release}` === branch.name;
189
+ return branchUpgrade ?
190
+ isTargetBranch || (branch.from.name <= `release/${r.release}` && `release/${r.release}` < branch.name && withinDateRange) :
191
+ isTargetBranch && withinDateRange
192
+ ;
193
+ }));
40
194
 
41
- if (argv.init) {
42
- const remoteTdiUrl = `${_paths.gitremote}/${_paths.tdi}.git`;
43
- let branches;
44
-
45
- try { branches = execSync('git ls-remote --heads ' + remoteTdiUrl, {encoding: 'UTF-8'}); }
46
- catch (e) { _error(' '); } // execSync already prints an error to the console
47
-
48
- branches = branches
49
- .match(/refs\/heads\/\S+/g)
50
- .map(s => s.replace('refs/heads/', ''))
51
- .sort((a, b) => {
52
- const aRelease = /^release\//.test(a), bRelease = /^release\//.test(b);
53
- if (aRelease && bRelease) return a > b ? -1 : 1;
54
- if (aRelease) return -1;
55
- if (bRelease) return 1;
56
- return a > b ? 1 : -1;
57
- })
58
- ;
59
- inquirer
60
- .prompt([{
61
- message: 'Choose a branch for TDI submodule: ', name: 'branch', type: 'list',
62
- choices: branches, pageSize: 3, loop: false
63
- }])
64
- .then(a => {
65
- _write();
66
- cmdExec([
67
- ['git init', 'Initialize repo:'],
68
- [`git remote add origin ${_paths.gitremote.replace(/\//, ':')}/${path.basename(process.cwd()).replace(/\W/, '-')}.git`],
69
- [`git submodule add -b ${a.branch} ${remoteTdiUrl}`, 'Add TDI submodule:'],
70
- ['git commit -m "added tdi submodule"', 'Commit:']
71
- ])
72
- .then(() => {
73
- _info(`Next steps:
74
- 1. Go to https://bitbucket.org/repo/create?workspace=tangelosoftware and create a repository named "${path.basename(process.cwd()).replace(/\W/, '-')}", with options for including README/.gitignore disabled.
75
- 2. Open this folder in Sourcetree (ctrl+O), and push master branch to origin.
76
- `.replace(/^\s{8}/gm, ''));
77
- });
195
+ let relevantMigrationCount = 0;
196
+ // Apply callback for migrations
197
+ migrationsFiltered
198
+ .sort((a, b) => Date(a.releases[0].date) > Date(b.releases[0].date) ? 1 : -1)
199
+ .forEach(m => {
200
+ const date = _formatDate(new Date(m.releases.filter(r => branch.name >= `release/${r.release}`)[0].date));
201
+ relevantMigrationCount += m.callback(date, relevantMigrationCount+1) === 1 ? 1 : 0;
78
202
  });
79
- }
80
-
81
- if (argv.reset) {
82
- _info('Reset and clean repo:');
83
- cmdExec([
84
- ['git reset --hard'],
85
- ['git clean -f -d']
86
- ]);
87
- }
88
-
89
- if (argv.clone) {
90
- if (typeof argv.clone !== 'string') _error('Pass a repository name!');
91
-
92
- _info('Clone a client repository and do basic project initialization');
93
- cmdExec([
94
- // Clone repository
95
- ['git clone git@bitbucket.org:tangelosoftware/' + argv.clone + '.git']
96
- ])
97
- .then(() => {
98
- // Change directory for the next commands, it should have the same name as the clone argument from commandline at this point
99
- process.chdir('./' + argv.clone);
100
- cmdExec([
101
- // Retrieve TDI submodule for this client
102
- ['git submodule update --init', 'Fetch submodules such as TDI'],
103
- // Create symlinks for TDI
104
- ['tct b -s', 'Create TDI symlinks'],
105
- // Create Oxygen project file and set name equal to repo name
106
- [`tct b -x ${argv.clone}`, 'Create Oxygen project file'],
107
- ]);
108
- })
109
- .catch(e => {throw e;})
110
- ;
111
- }
112
203
 
113
- if (argv.update) {
114
- const [update, tdiFromDateCustom, tdiToDateCustom] = `${argv.update}`.split(/\s/);
115
-
116
- if (update === `tdi`) {
117
-
118
- const tdiBranch = getTdiBranch(argv.branch);
119
- const tdiBranchUpgrade = tdiBranch.from ? (tdiBranch.from.name < tdiBranch.name) : false;
120
-
121
- if (tdiBranchUpgrade) _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)}`);
122
- _info(`Branch ${tdiBranch.name} is ${tdiBranch.commitsBehind} commits behind.`);
123
-
124
- // Set branch in .gitmodules file; This ensures submodule update will follow this branch to the latest commit
125
- const setBranch = execGitCommand(`submodule set-branch -b ${tdiBranch.name} tangelo-default-implementation`, _paths.repo);
126
- if (setBranch.error) _error(`Set branch failed: ${setBranch.error}`);
127
-
128
- if (tdiBranch.commitsBehind > 0 || tdiFromDateCustom) {
129
- // update submodule
130
- const updateSubmoduleMsg = execGitCommand(`submodule update --remote`, _paths.repo);
131
- if (updateSubmoduleMsg.error && !tdiFromDateCustom) _error(`Update submodule failed\n${updateSubmoduleMsg.error}`);
132
-
133
- const tdiDateBeforeUpdate = _git.commitTdi.local().date;
134
- // update local TDI commit data
135
- _git.commitTdi.local(true);
136
-
137
- if (!tdiFromDateCustom) _info(`TDI submodule updated:\n${updateSubmoduleMsg}`);
138
-
139
- // tdiMigrationFilePath should exist in latest commits of releases 5.3+; As we updated to the latest version this should work
140
- const migrations = _modulesTdi.require('git/tdiCommitsRequiringMigration.js');
141
- if (migrations) {
142
- const fromTdiDate = tdiFromDateCustom ? new Date(tdiFromDateCustom) : (tdiBranch.commonAncestor) ? tdiBranch.commonAncestor.date: tdiDateBeforeUpdate;
143
- const toTdiDate = tdiToDateCustom ? new Date(tdiToDateCustom) : new Date();
144
-
145
- _info(`TDI commits requiring migration between ${_formatDate(fromTdiDate)} and ${_formatDate(toTdiDate)}`);
146
- // Filter the migrations that should be applied/considered; Also display older releases migrations
147
- const migrationsFiltered = migrations
148
- .filter((m) => m.releases.find((r) => {
149
- const time = new Date(r.date).getTime();
150
- return ((tdiBranchUpgrade)
151
- ? `release/${r.release}` == tdiBranch.name || (tdiBranch.from.name <= `release/${r.release}` && `release/${r.release}` < tdiBranch.name && (fromTdiDate.getTime() < time && time < toTdiDate.getTime()))
152
- : `release/${r.release}` == tdiBranch.name && (fromTdiDate.getTime() < time && time < toTdiDate.getTime())
153
- );
154
- })
155
- );
156
-
157
- let relevantMigrationCount = 0;
158
- // Apply callback for migrations
159
- migrationsFiltered
160
- .sort((a, b) => Date(a.releases[0].date)>Date(b.releases[0].date)?1:-1)
161
- .forEach((m) => {
162
- const date = _formatDate(new Date(m.releases.filter((r) => tdiBranch.name >= `release/${r.release}`)[0].date));
163
- relevantMigrationCount += (m.callback(date, relevantMigrationCount+1)) === 1 ? 1 : 0;
164
- });
165
-
166
- // Output message based on whether or not migrations are required
167
- if (migrationsFiltered.length === 0) {
168
- _write(`-- No commits require migrations --`);
169
- } else {
170
- _info(`\n${relevantMigrationCount} relevant migration(s) out of ${migrationsFiltered.length} migration(s) are shown.`);
171
- _info(`See confluence page 'TDI commits requiring migration' for more detailed information`);
172
- }
173
-
174
- } else {
175
- _error(`Cannot find required files in TDI submodule.`);
176
- }
177
- } else {
178
- _info(`Your TDI submodule is already up to date`);
179
- }
204
+ // Output message based on whether or not migrations are required
205
+ if (migrationsFiltered.length === 0) {
206
+ _write(`-- No commits require migrations --`);
180
207
  } else {
181
- if (argv.branch) _warn(`Branch argument '${argv.branch}' is ignored for a repository update.`);
182
-
183
- // Fetch all
184
- _info(`Fetch all`);
185
- const cmdFetch = execGitCommand('fetch -pf --all', path.join(_paths.repo));
186
- if (cmdFetch.error) _warn(`Fetch failed\n${cmdFetch.error}`);
187
-
188
- // pull repo
189
- _info(`Pull repository`);
190
- const cmdPull = execGitCommand(`pull`, _paths.repo);
191
- if (cmdPull.error) _warn(`Pull failed\n${cmdPull.error}`);
192
-
193
- _info(`Checked out at commit:`);
194
- const repoLog = _git.commitLocal(true);
195
- _write(`${_formatDate(repoLog.date)} - ${repoLog.tags} - ${repoLog.hash}`);
196
-
197
- _info(`Retrieve submodules that belong to this repo-commit`); // update submodule recursively (set to version that belongs to repo)
198
- _write(execGitCommand(`submodule update --recursive`, path.join(_paths.repo)));
199
- _info(`Submodule checked out at commit:`);
200
- const tdiLog = _git.commitTdi.local(true);
201
- _write(`${_formatDate(tdiLog.date)} - ${tdiLog.tags} - ${tdiLog.hash}`);
202
- const tdiBranch = getTdiBranch();
203
-
204
- _info(`\nTDI branch ${tdiBranch.name} is ${tdiBranch.commitsBehind} commits behind.\nUpdate TDI to latest version with 'tct g -u tdi'`);
208
+ _info(`\n${relevantMigrationCount} relevant migration(s) out of ${migrationsFiltered.length} migration(s) are shown.`);
209
+ _info(`See confluence page 'TDI commits requiring migration' for more detailed information (https://tangelo-software.atlassian.net/wiki/x/qQnE)`);
205
210
  }
211
+
212
+ } else {
213
+ _info(`Your TDI submodule is already up to date`);
206
214
  }
215
+ }
216
+
217
+
218
+ module.exports = function git (argv) {
219
+
220
+ if (argv.init) init();
221
+ if (argv.reset) reset();
222
+ if (argv.clone) clone(argv);
223
+ if (argv.updateRepo) updateRepo();
224
+ if (argv.updateSubmodule) updateSubmodule(argv);
225
+
207
226
  };
@@ -51,7 +51,7 @@ module.exports = function migrate (argv) {
51
51
  a.script = cmdlScript.value;
52
52
  }
53
53
 
54
- let filter = path.join(_paths.apply, argv.filter).toFws(); // Default set filter with filter added to the command argument -f
54
+ let filter = path.join(_paths.apply, argv.filter??'**').toFws(); // Default set filter with filter added to the command argument -f
55
55
 
56
56
  if (a.filter === 'projects' && _repoconfig.length > a.projects.length) {
57
57
  // push paths of chosen active documents to projectFolders (only if not all projects are chosen)