@tangelo/tangelo-configuration-toolkit 1.15.1 → 1.17.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
@@ -26,7 +26,8 @@ Use the `tct` shorthand instead of `tangelo-configuration-toolkit`:
26
26
 
27
27
  ### Global
28
28
 
29
- The `deploy` command requires server connection information. So you'll have to create a configuration file named `tangelo-configuration-toolkit-appconfig.json` in which such information can be stored. This file will be searched for in an ancestor directory of the current working directory.
29
+ TCT requires a config file to work: `${userhome}/.tct/appconfig.json`\
30
+ Several commands, like the `deploy` command, require server connection information, which can be stored in here.
30
31
 
31
32
  The contents looks like this (all properties are optional):
32
33
 
@@ -53,12 +54,6 @@ The contents looks like this (all properties are optional):
53
54
 
54
55
  When passing a server name, `tct` will look for a full match with a name or a partial match (the start) with a domain.
55
56
 
56
- ### Repo
57
-
58
- The `build` and `sql` commands make use of a configuration file in the repository named `tangelo-configuration-toolkit-repoconfig.json`. This contains information about the customer projects.
59
-
60
- For a new repository, using `tct build --init` also creates the repoconfig-file. For existing projects not having the repoconfig-file, you can use `tct build --config` to generate it.
61
-
62
57
  ### oXygen
63
58
 
64
59
  The `build -x` commands set projects transformation scenarios and masterfiles in the oXygen project file with the following functionality:
package/index.js CHANGED
@@ -8,6 +8,7 @@ String.prototype.toFws = function(){
8
8
  const {execSync} = require('child_process');
9
9
  const findUp = require('find-up');
10
10
  const fs = require('fs-extra');
11
+ const homedir = require('os').homedir();
11
12
  const path = require('path');
12
13
 
13
14
  const execGitCommand = require('./src/lib/exec-git-command');
@@ -45,17 +46,25 @@ global._perf = t1 => {
45
46
  };
46
47
 
47
48
  global._formatDate = date =>
48
- date?.toLocaleDateString('en-gb', {day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'});
49
+ date?.toLocaleDateString('en-gb', {day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit'})
49
50
  ;
50
51
 
52
+
53
+ global._packages = {
54
+ TCT: {name: '@tangelo/tangelo-configuration-toolkit', version: require('./package.json')?.version},
55
+ FDT: {name: '@fontoxml/fontoxml-development-tools'}
56
+ };
57
+
58
+
51
59
  global._paths = {
52
60
  app: __dirname,
53
- appdata: path.resolve(__dirname, '..', appname+'-appdata.json'),
54
- appconfig: findUp.sync(appname+'-appconfig.json'),
61
+ apphome: path.join(homedir, '.tct'),
55
62
  repo: findUp.sync(dir => fs.existsSync(path.join(dir, '.git')) && dir, {type: 'directory'}) || '',
56
63
  tdi: 'tangelo-default-implementation',
57
64
  gitremote: 'git@bitbucket.org:tangelosoftware'
58
65
  };
66
+ _paths.appdata = path.join(_paths.apphome, 'appdata.json');
67
+ _paths.appconfig = path.join(_paths.apphome, 'appconfig.json');
59
68
  _paths.repoconfig = path.join(_paths.repo, appname+'-repoconfig.json');
60
69
  _paths.apply = process.cwd().replace(_paths.repo, '').substr(1);
61
70
 
@@ -66,15 +75,37 @@ if (!_appdata.npmPath) {
66
75
  _appdata._update({npmPath: execSync('npm config get prefix', {encoding: 'UTF-8'}).replace(/\s$/, '')});
67
76
  }
68
77
 
69
- global._packages = {
70
- TCT: {name: '@tangelo/tangelo-configuration-toolkit', version: require('./package.json')?.version},
71
- FDT: {name: '@fontoxml/fontoxml-development-tools'}
78
+
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
+ global._appconfig = { // default appconfig if file not found
89
+ 'sharedConfigPath': path.join(homedir, 'Dropbox (Tangelo Software)/Product Distribution/tooling'),
90
+ 'servers': [],
91
+ 'defaultServer': 'tdpXX',
92
+ 'defaultDatabase': 'tdpXX'
72
93
  };
73
94
 
74
- try { global._appconfig = _paths.appconfig && fs.readJsonSync(_paths.appconfig) || {}; }
75
- catch({message}) { _error('Error in '+message); }
95
+ try {
96
+ global._appconfig = fs.readJsonSync(_paths.appconfig);
97
+ }
98
+ catch({code, message}) {
99
+ if (code === 'ENOENT') { // create appconfig json if not exists
100
+ fs.ensureDirSync(_paths.apphome);
101
+ fs.writeJsonSync(_paths.appconfig, global._appconfig, {spaces: 2});
102
+ _info(`${_paths.appconfig} was created`);
103
+ }
104
+ else _error('Could not load app-config: '+message);
105
+ }
106
+
76
107
 
77
- _appconfig.sharedConfigPath = path.resolve(_paths.appconfig || '', '..', _appconfig.sharedConfigPath || '', appname+'-appconfig.json');
108
+ _appconfig.sharedConfigPath = path.resolve(_paths.appconfig, '..', _appconfig.sharedConfigPath || '', appname+'-appconfig.json');
78
109
  _appconfig.shared = fs.readJsonSync(_appconfig.sharedConfigPath, {throws: false}) || {};
79
110
 
80
111
 
@@ -83,7 +114,8 @@ global._git = {
83
114
  if (!_appdata.gitUser) _appdata._update({gitUser: execGitCommand(`config --get user.email`, _paths.repo)});
84
115
  return _appdata.gitUser;
85
116
  },
86
- commitLocal () {
117
+ commitLocal (clearcache) {
118
+ if (clearcache) delete this.cache;
87
119
  this.cache ??= execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, _paths.repo, ['branch', 'hash', 'date']);
88
120
  return this.cache;
89
121
  },
@@ -92,7 +124,8 @@ global._git = {
92
124
  return this.cache;
93
125
  },
94
126
  commitTdi: {
95
- local () {
127
+ local (clearcache) {
128
+ if (clearcache) delete this.cache;
96
129
  this.cache ??= execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, path.join(_paths.repo, _paths.tdi), ['tags', 'hash', 'date']);
97
130
  return this.cache;
98
131
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangelo/tangelo-configuration-toolkit",
3
- "version": "1.15.1",
3
+ "version": "1.17.0",
4
4
  "engines": {
5
5
  "node": ">=14.0.0"
6
6
  },
package/src/cli.js CHANGED
@@ -8,13 +8,22 @@ module.exports = function cli () {
8
8
  const txtTitle = 'Tangelo Configuration Toolkit'.bold.underline.cyan;
9
9
  const txtVersion = `v${_packages.TCT.version}`.lblack;
10
10
 
11
- const {argv} = yargs
11
+ yargs
12
12
  .scriptName('tct')
13
13
  .usage(`${txtTitle} ${txtVersion}\n\nUsage: $0 <command> [options]`)
14
14
  .middleware(argv => { // executes before command handlers
15
- if (!(['git', 'g'].includes(argv._[0]) && (argv.init || argv.clone))) {
16
- if (!_paths.repo) _error('This command can only be used inside a customer git repo!');
17
- process.chdir(_paths.repo);
15
+ const command = argv._[0];
16
+ const optionsPassed = Object.keys(argv).length > 2;
17
+ const gitInitOrClone = (command === 'git' || command === 'g') && (argv.init || argv.clone);
18
+
19
+ if (!command && argv.config) {
20
+ _info(`Loaded appconfig:`);
21
+ _write(_paths.appconfig);
22
+ _write(_appconfig);
23
+ }
24
+ else if (optionsPassed && !gitInitOrClone) {
25
+ if (_tdiSubmoduleExists()) process.chdir(_paths.repo); // set cwd to repo root before executing an option
26
+ else _error('This option can only be used inside a git repo having the TDI submodule!');
18
27
  }
19
28
  })
20
29
  .command({
@@ -79,7 +88,7 @@ module.exports = function cli () {
79
88
  builder: {
80
89
  execute: {alias: 'e', desc: 'Execute migration'},
81
90
  dry: {alias: 'd', desc: 'Dry run (does not persist changes)'},
82
- filter: {alias: 'f', desc: 'Pass glob filter (only used for applicable steps)', default: '**'}
91
+ filter: {alias: 'f', desc: 'Pass glob filter (only used for applicable steps)'}
83
92
  },
84
93
  handler: require('./modules/migrate')
85
94
  })
@@ -121,21 +130,11 @@ module.exports = function cli () {
121
130
  .check((argv, options) => {
122
131
  const nonDefaultOptions = Object.keys(options.key).filter(o => !Object.keys(options.default).includes(o));
123
132
  if (nonDefaultOptions.some(o => argv[o])) return true;
124
- else throw new Error("Pass a non-default option");
133
+ else throw new Error('Pass a non-default option');
125
134
  })
126
135
  .strict()
127
136
  .wrap(100)
137
+ .parse()
128
138
  ;
129
139
 
130
-
131
- if (!argv._[0]) { // no command chosen
132
-
133
- if (argv.config) {
134
- if (!_paths.appconfig) _error('No config loaded: tangelo-configuration-toolkit-appconfig.json not found');
135
- _info(`Loaded appconfig (using ${_paths.appconfig}):`);
136
- _write(_appconfig);
137
- }
138
-
139
- }
140
-
141
140
  };
@@ -29,17 +29,16 @@ const createSymlinks = () => {
29
29
 
30
30
  module.exports = function build (argv) {
31
31
 
32
- if (argv.init) {
33
- if (_isPre51()) _error('This option only works when using branch release/5.1 and up.');
32
+ if ((argv.init || argv.project) && _isPre51()) _error('This option only works when using branch release/5.1 and up.');
34
33
 
35
- _modulesTdi.require('build/init')(createSymlinks);
36
- }
37
-
38
- if (argv.project) {
39
- if (_isPre51()) _error('This option only works when using branch release/5.1 and up.');
34
+ const predefinedAnswers = (argv.init === 'file' || argv.project === 'file') ? _appconfig.build : undefined;
35
+ // if not chosen to use predefined values by passing "file", erase the "build" property for old TDI commits
36
+ _appconfig.build = predefinedAnswers;
40
37
 
41
- const {projectNew, projectCopy} = _modulesTdi.require('build/project');
38
+ if (argv.init) _modulesTdi.require('build/init')(createSymlinks, predefinedAnswers);
42
39
 
40
+ if (argv.project === 'file') _modulesTdi.require('build/project').projectNew(createSymlinks, _repoconfig[0], predefinedAnswers);
41
+ else if (argv.project) {
43
42
  inquirer
44
43
  .prompt([{
45
44
  message: 'Create project: ', name: 'project', type: 'list',
@@ -53,10 +52,8 @@ module.exports = function build (argv) {
53
52
  ]
54
53
  }])
55
54
  .then(a => {
56
- if (a.project=='TDI') projectNew(createSymlinks);
57
- else {
58
- projectCopy(createSymlinks, a.project);
59
- }
55
+ if (a.project === 'TDI') _modulesTdi.require('build/project').projectNew(createSymlinks, _repoconfig[0]);
56
+ else _modulesTdi.require('build/project').projectCopy(createSymlinks, a.project);
60
57
  });
61
58
  }
62
59
 
@@ -76,7 +73,6 @@ module.exports = function build (argv) {
76
73
  if (argv.config) {
77
74
  if (_git.commitTdi.after(_git.commitTdi.stopUsingRepoconfigFile)) _error('This option only works for older repo\'s using a repoconfig file.');
78
75
 
79
-
80
76
  inquirer
81
77
  .prompt([{message: 'Be sure paths for projects are [customer]/[project]. Continue?', name: 'confirm', type: 'confirm'}])
82
78
  .then(({confirm}) => {
@@ -1,5 +1,7 @@
1
+ const del = require('del');
1
2
  const {exec} = require('child_process');
2
3
  const fs = require('fs-extra');
4
+ const globby = require('globby');
3
5
  const gulp = require('gulp');
4
6
  const g_print = require('gulp-print');
5
7
  const path = require('path');
@@ -219,15 +221,27 @@ module.exports = {
219
221
  ;
220
222
 
221
223
  _info(`Ensure ${messagesFile} exists:`);
222
- if (!fs.existsSync(messagesFile)) fs.outputJsonSync(messagesFile, [], {spaces: 2});
224
+ if (!fs.existsSync(messagesFile)) fs.outputJsonSync(messagesFile, []);
225
+ _write('Done.\n');
226
+
227
+ _info('Make temporary copies of json files:');
228
+ const attrCfgTmpFiles = globby.sync('packages/*/src/!(messages.|operations-)*.json');
229
+ attrCfgTmpFiles.forEach((p, i, a) => {
230
+ _write(p);
231
+ a[i] = path.join(path.dirname(p), 'operations-' + path.basename(p));
232
+ fs.copyFileSync(p, a[i]);
233
+ });
234
+ _write();
223
235
 
224
236
  return cmdExec(`${fdt} localization extract ${templateFile} --paths ` + packagesDirs)
225
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]
226
240
  .then(() => {
227
- _info('Remove template file:');
228
- fs.unlinkSync(templateFile);
229
- _write('Done.\n');
241
+ _info('Cleanup temp files:');
242
+ return del([templateFile, ...attrCfgTmpFiles]);
230
243
  })
244
+ .then(() => _write('Done.\n'))
231
245
  .then(() => [fdt])
232
246
  ;
233
247
  },
@@ -19,8 +19,6 @@ module.exports = function fonto (argv) {
19
19
  }
20
20
  })();
21
21
 
22
- if (!_tdiSubmoduleExists()) _error('TDI submodule folder is missing.');
23
-
24
22
  // check if FDT is not installed globally, because then it won't be possible to use specific versions with npx
25
23
  if (fs.pathExistsSync(path.join(_appdata.npmPath, 'node_modules', _packages.FDT.name))) {
26
24
  _error(`A global installation of FDT has been found! Remove it first.\nExecute: npm r -g ${_packages.FDT.name}`);
@@ -1,8 +1,6 @@
1
1
  const {execSync, spawn} = require('child_process');
2
- const {chdir} = require('process');
3
- const inquirer = require('inquirer');
4
- const path = require('path');
5
- const fs = require('fs-extra');
2
+ const inquirer = require('inquirer');
3
+ const path = require('path');
6
4
 
7
5
  const execGitCommand = require('../../lib/exec-git-command');
8
6
  const getTdiBranch = require('../../lib/get-tdi-branch');
@@ -15,7 +13,8 @@ const cmdExec = commands => new Promise(resolve => {
15
13
 
16
14
  const log = msg => {
17
15
  const line = msg.replace(/\s+$/, ''); // remove excessive whitespace
18
- if (line) _write(line);
16
+ if (line?.startsWith('fatal:')) _error(line);
17
+ else if (line) _write(line);
19
18
  };
20
19
 
21
20
  const cmdArr = command.split(/ (.+)/);
@@ -88,7 +87,7 @@ module.exports = function git (argv) {
88
87
  }
89
88
 
90
89
  if (argv.clone) {
91
- if (argv.clone === true) _error('Pass a repository name!');
90
+ if (typeof argv.clone !== 'string') _error('Pass a repository name!');
92
91
 
93
92
  _info('Clone a client repository and do basic project initialization');
94
93
  cmdExec([
@@ -96,8 +95,8 @@ module.exports = function git (argv) {
96
95
  ['git clone git@bitbucket.org:tangelosoftware/' + argv.clone + '.git']
97
96
  ])
98
97
  .then(() => {
99
- // Change directory for the next commands, it should have the same name as the clone argument from commandline at this point
100
- chdir('./' + argv.clone);
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);
101
100
  cmdExec([
102
101
  // Retrieve TDI submodule for this client
103
102
  ['git submodule update --init', 'Fetch submodules such as TDI'],
@@ -106,17 +105,9 @@ module.exports = function git (argv) {
106
105
  // Create Oxygen project file and set name equal to repo name
107
106
  [`tct b -x ${argv.clone}`, 'Create Oxygen project file'],
108
107
  ]);
109
- }).catch((e) => {
110
- // Show warnings for missing, expected, system environment variables
111
- if (!process.env.GIT_SSH) {
112
- _warn(`System environment variable 'GIT_SSH' is missing. (link to putty plink.exe)`);
113
- }
114
- if (!process.env.GIT_SSH_COMMAND) {
115
- _warn(`System environment variable 'GIT_SSH_COMMAND' is missing. (link to putty plink.exe)`);
116
- }
117
- // continue display stack trace
118
- throw e;
119
- });
108
+ })
109
+ .catch(e => {throw e;})
110
+ ;
120
111
  }
121
112
 
122
113
  if (argv.update) {
@@ -139,12 +130,16 @@ module.exports = function git (argv) {
139
130
  const updateSubmoduleMsg = execGitCommand(`submodule update --remote`, _paths.repo);
140
131
  if (updateSubmoduleMsg.error && !tdiFromDateCustom) _error(`Update submodule failed\n${updateSubmoduleMsg.error}`);
141
132
 
133
+ const tdiDateBeforeUpdate = _git.commitTdi.local().date;
134
+ // update local TDI commit data
135
+ _git.commitTdi.local(true);
136
+
142
137
  if (!tdiFromDateCustom) _info(`TDI submodule updated:\n${updateSubmoduleMsg}`);
143
138
 
144
139
  // tdiMigrationFilePath should exist in latest commits of releases 5.3+; As we updated to the latest version this should work
145
140
  const migrations = _modulesTdi.require('git/tdiCommitsRequiringMigration.js');
146
141
  if (migrations) {
147
- const fromTdiDate = tdiFromDateCustom ? new Date(tdiFromDateCustom) : (tdiBranch.commonAncestor) ? tdiBranch.commonAncestor.date: _git.commitTdi.local().date;
142
+ const fromTdiDate = tdiFromDateCustom ? new Date(tdiFromDateCustom) : (tdiBranch.commonAncestor) ? tdiBranch.commonAncestor.date: tdiDateBeforeUpdate;
148
143
  const toTdiDate = tdiToDateCustom ? new Date(tdiToDateCustom) : new Date();
149
144
 
150
145
  _info(`TDI commits requiring migration between ${_formatDate(fromTdiDate)} and ${_formatDate(toTdiDate)}`);
@@ -162,6 +157,7 @@ module.exports = function git (argv) {
162
157
  let relevantMigrationCount = 0;
163
158
  // Apply callback for migrations
164
159
  migrationsFiltered
160
+ .sort((a, b) => Date(a.releases[0].date)>Date(b.releases[0].date)?1:-1)
165
161
  .forEach((m) => {
166
162
  const date = _formatDate(new Date(m.releases.filter((r) => tdiBranch.name >= `release/${r.release}`)[0].date));
167
163
  relevantMigrationCount += (m.callback(date, relevantMigrationCount+1)) === 1 ? 1 : 0;
@@ -195,13 +191,13 @@ module.exports = function git (argv) {
195
191
  if (cmdPull.error) _warn(`Pull failed\n${cmdPull.error}`);
196
192
 
197
193
  _info(`Checked out at commit:`);
198
- const repoLog = execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, _paths.repo, ['tags', 'hash', 'date']);
194
+ const repoLog = _git.commitLocal(true);
199
195
  _write(`${_formatDate(repoLog.date)} - ${repoLog.tags} - ${repoLog.hash}`);
200
196
 
201
197
  _info(`Retrieve submodules that belong to this repo-commit`); // update submodule recursively (set to version that belongs to repo)
202
198
  _write(execGitCommand(`submodule update --recursive`, path.join(_paths.repo)));
203
199
  _info(`Submodule checked out at commit:`);
204
- const tdiLog = execGitCommand(`log -1 --format=%D;%H;%cd --date=iso-strict`, path.join(_paths.repo, _paths.tdi), ['tags', 'hash', 'date']);
200
+ const tdiLog = _git.commitTdi.local(true);
205
201
  _write(`${_formatDate(tdiLog.date)} - ${tdiLog.tags} - ${tdiLog.hash}`);
206
202
  const tdiBranch = getTdiBranch();
207
203
 
@@ -6,8 +6,6 @@ const path = require('path');
6
6
  module.exports = function migrate (argv) {
7
7
  if (_isPre51()) _error('This command only works when using branch release/5.1 and up.');
8
8
 
9
- let filter = path.join(_paths.apply, argv.filter).toFws(); // Default set filter with filter added to the command argument -f
10
-
11
9
  const scripts =
12
10
  globby
13
11
  .sync(_paths.tdi + '/tct/migrate/*.js')
@@ -37,12 +35,12 @@ module.exports = function migrate (argv) {
37
35
  when: !(cmdlScript) // Only show choice for migration when no script is set in commandline
38
36
  }, {
39
37
  message: 'Choose filter: ', name: 'filter', type: 'list', pageSize: 5, loop: false,
40
- choices: [{name: `- All`, value: argv.filter}, {name: '- Choose active projects', value: 'projects'}],
41
- when: argv.filter === '**' // Only show this message if no custom filter was added to the command argument -f
38
+ choices: [{name: `- All`, value: '**'}, {name: '- Choose active projects', value: 'projects'}],
39
+ when: !argv.filter // Only show this message if no custom filter was added to the command argument -f
42
40
  }, {
43
41
  message: 'Choose active projects: ', name: 'projects', type: 'checkbox', validate: v => !!v[0],
44
42
  choices: _repoconfig.map(p => ({name: `${p.customer_name} ${p.project_name}`, value: p})),
45
- when: (a) => a.filter === 'projects' // Only show this message if 'Choose active projects' was chosen previously
43
+ when: a => a.filter === 'projects' // Only show this message if 'Choose active projects' was chosen previously
46
44
  }])
47
45
  .then(a => {
48
46
  _write();
@@ -53,6 +51,8 @@ module.exports = function migrate (argv) {
53
51
  a.script = cmdlScript.value;
54
52
  }
55
53
 
54
+ let filter = path.join(_paths.apply, argv.filter).toFws(); // Default set filter with filter added to the command argument -f
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)
58
58
  const projectFolders = [];