@tangelo/tangelo-configuration-toolkit 1.13.0 → 1.14.1

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
@@ -1,67 +1,67 @@
1
- # tangelo-configuration-toolkit
2
-
3
- Tangelo Configuration Toolkit is a command-line toolkit which offers support for developing a Tangelo configuration.
4
-
5
- ## Installation
6
-
7
- The toolkit requires [NPM](https://www.npmjs.com/get-npm) on [Node.js®](https://nodejs.org/) (at least version 14.x). An active or maintenance LTS release is recommended. After installing Node.js, you can install the latest version of the Tangelo Configuration Toolkit globally on your system using the following command:
8
-
9
- npm i -g @tangelo/tangelo-configuration-toolkit
10
-
11
- ## Usage
12
-
13
- Get help for the available commands and see version:
14
-
15
- tangelo-configuration-toolkit
16
-
17
- Get help for a specific command, detailing all its arguments:
18
-
19
- tangelo-configuration-toolkit <command>
20
-
21
- Use the `tct` shorthand instead of `tangelo-configuration-toolkit`:
22
-
23
- tct <command>
24
-
25
- ## Config
26
-
27
- ### Global
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.
30
-
31
- The contents looks like this (all properties are optional):
32
-
33
- {
34
- "sharedConfigPath": "absolute/or/relative/path/to/folder/containing/shared/config",
35
- "servers": [{
36
- "config": {
37
- "port": 22,
38
- "parallel": 4,
39
- "username": "username",
40
- "remotedir": "/absolute/path/to/tangelo/config/folder/on/server"
41
- },
42
- "domains": ["domain.name.com"],
43
- "name": "name-for-local-deploy"
44
- }],
45
- "serverDefaults": {
46
- "config": {
47
- ...
48
- }
49
- }
50
- "defaultServer": "can be set to the name of e.g. your favorite dev server",
51
- "defaultDatabase": "can be set to the tnsname of e.g. your favorite dev server"
52
- }
53
-
54
- 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
- ### 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
- ### oXygen
63
-
64
- The `build -x` commands set projects transformation scenarios and masterfiles in the oXygen project file with the following functionality:
65
- - Will try to preserve manually added entries in the transformation scenarios and masterfiles
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
1
+ # tangelo-configuration-toolkit
2
+
3
+ Tangelo Configuration Toolkit is a command-line toolkit which offers support for developing a Tangelo configuration.
4
+
5
+ ## Installation
6
+
7
+ The toolkit requires [NPM](https://www.npmjs.com/get-npm) on [Node.js®](https://nodejs.org/) (at least version 14.x). An active or maintenance LTS release is recommended. After installing Node.js, you can install the latest version of the Tangelo Configuration Toolkit globally on your system using the following command:
8
+
9
+ npm i -g @tangelo/tangelo-configuration-toolkit
10
+
11
+ ## Usage
12
+
13
+ Get help for the available commands and see version:
14
+
15
+ tangelo-configuration-toolkit
16
+
17
+ Get help for a specific command, detailing all its arguments:
18
+
19
+ tangelo-configuration-toolkit <command>
20
+
21
+ Use the `tct` shorthand instead of `tangelo-configuration-toolkit`:
22
+
23
+ tct <command>
24
+
25
+ ## Config
26
+
27
+ ### Global
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.
30
+
31
+ The contents looks like this (all properties are optional):
32
+
33
+ {
34
+ "sharedConfigPath": "absolute/or/relative/path/to/folder/containing/shared/config",
35
+ "servers": [{
36
+ "config": {
37
+ "port": 22,
38
+ "parallel": 4,
39
+ "username": "username",
40
+ "remotedir": "/absolute/path/to/tangelo/config/folder/on/server"
41
+ },
42
+ "domains": ["domain.name.com"],
43
+ "name": "name-for-local-deploy"
44
+ }],
45
+ "serverDefaults": {
46
+ "config": {
47
+ ...
48
+ }
49
+ }
50
+ "defaultServer": "can be set to the name of e.g. your favorite dev server",
51
+ "defaultDatabase": "can be set to the tnsname of e.g. your favorite dev server"
52
+ }
53
+
54
+ 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
+ ### 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
+ ### oXygen
63
+
64
+ The `build -x` commands set projects transformation scenarios and masterfiles in the oXygen project file with the following functionality:
65
+ - Will try to preserve manually added entries in the transformation scenarios and masterfiles
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
package/bin/index.js CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
2
-
1
+ #!/usr/bin/env node
2
+
3
3
  require('..');
@@ -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.13.0",
3
+ "version": "1.14.1",
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
  })
@@ -111,10 +113,13 @@ module.exports = function cli () {
111
113
  .example([
112
114
  ['$0 deploy --copy --server demo', 'Copy files to server "demo"'],
113
115
  ['$0 d -c -s demo', 'Same as above'],
114
- ['$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']
115
120
  ])
116
121
  .check((argv, options) => {
117
- 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
118
123
  const hasOpts = Object.keys(options)[0];
119
124
  if (hasOpts && !passedOpts[0]) yargs.showHelp();
120
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,46 @@
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
+ // Fetch all
17
+ _info(`Fetch TDI submodule`);
18
+ const cmdFetch = execGitCommand('fetch -pf --all', path.join(_paths.repo, _paths.tdi));
19
+ if (cmdFetch.error) _warn(`Fetch failed\n${cmdFetch.error}`);
20
+
21
+ // Get remote release branches containing TDI HEAD commit
22
+ const releaseBranches = execGitCommand(`branch --all --contains ${_git.commitTdi.local().hash}`, path.join(_paths.repo, _paths.tdi)).match(/remotes\/origin\/release\/[^\s]+/gsm);
23
+ if (!releaseBranches || releaseBranches.error) _error(`Could not retrieve TDI release branches`);
24
+
25
+ // Get the first possible branch; prefer release/5.1 over release/5.2:
26
+ releaseBranches.sort((a, b) => a > b ? 1 : -1);
27
+
28
+ // Set branch name of firstBranch without 'remotes/origin/'
29
+ tdiBranch.name = releaseBranches[0].replace(/remotes\/origin\//g, '');
30
+
31
+ // In case of branch switch set from.name to the old branch and name to the new branch
32
+ if (toBranch) {
33
+ if (tdiBranch.name > toBranch) _error(`You cannot downgrade to a lower release branch with TCT.`);
34
+ tdiBranch.from = {name: tdiBranch.name};
35
+ tdiBranch.name = toBranch;
36
+
37
+ const branchHash = execGitCommand(`rev-parse origin/${toBranch}`, path.join(_paths.repo, _paths.tdi));
38
+ const commonAncestorHash = execGitCommand(`merge-base ${_git.commitTdi.local().hash} ${branchHash}`, path.join(_paths.repo, _paths.tdi));
39
+ const commonAncestorDate = execGitCommand(`show ${commonAncestorHash} --no-patch --format=%cd --date=iso-strict `, path.join(_paths.repo, _paths.tdi), ['date']).date;
40
+ tdiBranch.commonAncestor = {date: new Date(commonAncestorDate)};
41
+ }
42
+
43
+ // Get number of commits behind
44
+ tdiBranch.commitsBehind = execGitCommand(`rev-list HEAD...origin/${tdiBranch.name} --count`, path.join(_paths.repo, _paths.tdi));
45
+ return tdiBranch;
46
+ };
@@ -1,19 +1,19 @@
1
- // based on gulp-batch-replace
2
-
3
- const es = require('event-stream'), minimatch = require('minimatch'), istextorbinary = require('istextorbinary');
4
-
5
- const execReplace = (c, s, r) => Buffer.from(s instanceof RegExp ? String(c).replace(s, r) : String(c).split(s).join(r));
6
-
7
- module.exports = (arr) => {
8
- return es.map((file, callback) => {
9
- if(file.contents instanceof Buffer) {
10
- arr.forEach(e => {
11
- // exec if no glob is passed or if glob matches, and it's a text file
12
- if ((!e[2] || minimatch(file.path, e[2])) && istextorbinary.isText(file.path, file)) {
13
- file.contents = execReplace(file.contents, e[0], e[1]);
14
- }
15
- });
16
- }
17
- callback(null, file);
18
- });
19
- };
1
+ // based on gulp-batch-replace
2
+
3
+ const es = require('event-stream'), minimatch = require('minimatch'), istextorbinary = require('istextorbinary');
4
+
5
+ const execReplace = (c, s, r) => Buffer.from(s instanceof RegExp ? String(c).replace(s, r) : String(c).split(s).join(r));
6
+
7
+ module.exports = (arr) => {
8
+ return es.map((file, callback) => {
9
+ if(file.contents instanceof Buffer) {
10
+ arr.forEach(e => {
11
+ // exec if no glob is passed or if glob matches, and it's a text file
12
+ if ((!e[2] || minimatch(file.path, e[2])) && istextorbinary.isText(file.path, file)) {
13
+ file.contents = execReplace(file.contents, e[0], e[1]);
14
+ }
15
+ });
16
+ }
17
+ callback(null, file);
18
+ });
19
+ };
@@ -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
  }
@@ -1,11 +1,11 @@
1
- // replacement for the official (but deprecated) gulp-simple-rename
2
- // kept same name because of usage in tdi
3
-
4
- const through2 = require('through2');
5
-
6
- module.exports = (fn) => {
7
- return through2.obj((file, enc, cb) => {
8
- file.path = fn(file.path);
9
- cb(null, file);
10
- });
11
- };
1
+ // replacement for the official (but deprecated) gulp-simple-rename
2
+ // kept same name because of usage in tdi
3
+
4
+ const through2 = require('through2');
5
+
6
+ module.exports = (fn) => {
7
+ return through2.obj((file, enc, cb) => {
8
+ file.path = fn(file.path);
9
+ cb(null, file);
10
+ });
11
+ };
@@ -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
  });
@@ -9,7 +9,7 @@ const createSymlink = (t, p) => {
9
9
  fs.symlink(path.join(up, _paths.tdi, t), p, 'dir', err => {
10
10
  if (err) {
11
11
  if (err.code=='EEXIST') _write(`Symlink already exists: ${p}`);
12
- else _error((err.code=='EPERM' ? 'Start your console as admin for symlink creation!' : err));
12
+ else _error((err.code!=='EPERM' ? err : `Can't create symlinks, restart your console as admin, or fix it permanently:\n 1. Open 'Local Security Policy' (secpol.msc)\n 2. Go to 'Security Settings > Local Policies > User Rights Assignment > Create symbolic links'\n 3. Add your own account\n 4. Restart Windows`));
13
13
  }
14
14
  else _write(`Symlink created: ${p}`);
15
15
  });