@tangelo/tangelo-configuration-toolkit
Version:
Tangelo Configuration Toolkit is a command-line toolkit which offers support for developing a Tangelo configuration.
135 lines (109 loc) • 6.35 kB
JavaScript
const fs = require('fs-extra');
const objMerge = require('object-assign-deep');
const path = require('path');
function getRemotePath (...arr) {
const fp = path.join(this.server.remotedir, ...arr);
return this.server.remotedir.includes(':') ? fp : fp.toFws;
}
function setServer (searchString) {
if (!searchString) _error('No server was passed.');
const stringsToArrays = stringOrArray => [stringOrArray].flat();
const filterDomains = domain => domain[0].startsWith(searchString) || domain.slice(1).includes(searchString);
const filterServerConfigs = (arr, cfg) => {
if (cfg.domains) cfg.domains = cfg.domains.map(stringsToArrays).filter(filterDomains);
if (cfg.name === searchString || cfg.domains?.[0]) arr.push(cfg);
return arr;
};
// find server in local config and filter domains
let serverConfigs = _appconfig.servers?.reduce(filterServerConfigs, []);
// if nothing found, find server in shared config and filter domains
if (!serverConfigs?.[0]) serverConfigs = _appconfig.shared.servers?.reduce(filterServerConfigs, []);
// abort if no config is found, or if multiple configs or domains are found for server
if (!serverConfigs?.[0]) _error(`No config found for "${searchString}".`);
if (serverConfigs.length > 1 || serverConfigs[0].domains?.length > 1) {
const matches = serverConfigs.map(obj => obj.name === searchString ? obj.name : obj.domains);
const matchesFlatten = matches.flat().map(stringsToArrays).map(m => m[0] + (m[1] ? ` [${m.slice(1).join(', ')}]` : '')).join(', ');
_error(`Passed server name "${searchString}" gives multiple results:\n${matchesFlatten}`);
}
// complement serverconfig with defaults
const serverConfig = objMerge(
{config: {}, domains: [[]]}, // make sure keys are present
_appconfig.shared.serverDefaults, // override with shared defaults
_appconfig.serverDefaults, // override with local defaults
serverConfigs[0]
);
// compose final server config in usable format
const {config, domains: [[host]], mode} = serverConfig;
this.server = {remotedir: config.remotedir};
if (host) {
const {parallel, port, username} = config;
if (!(port && parallel && username)) _error('Config is incomplete!');
this.server.ftpConfig = {
host, port, parallel, username,
agent: process.env.SSH_AUTH_SOCK ?? process.platform === 'win32' ? '\\\\.\\pipe\\openssh-ssh-agent' : undefined,
agentForward: true,
readyTimeout: 15000,
retries: 1
};
}
if (mode === 'delivery-pack') {
const dateStr = new Date().toISOString().replace(/(.*)T(\d+):(\d+):(\d+).*/g, '$1_$2$3$4');
this.deliveryPackName = `tangelo-config-delivery-pack_${_paths.repo.split(/[\\/]/).pop()}_${dateStr}`;
this.server.remotedir += '/' + this.deliveryPackName; // output to temp folder
this.deliveryPack = true;
}
this.envDev = !host && !this.deliveryPack || /\.dev\.tangelo\.nl$/.test(host);
}
function prepareForCopy (filter) {
const {remotedir, ftpConfig} = this.server;
if (!path.isAbsolute(remotedir)) _error('"remotedir" must contain an absolute path!');
this.giPatterns = !fs.existsSync('.gitignore') ? [] :
fs.readFileSync('.gitignore').toString()
.trim()
.replace(/^#.+/gm, '') // remove comments
.split(/\s+/) // split on lines
.filter(p => !p.match(/(^database\/)|(\/tdi)|(\/fonto)/)) // filter paths that should be copied
.map(p => '!' + (p.match(/^config/) ? p : '**/' + p.split('**/').pop()) + (p.match(/\/$/) ? '**' : '')) // negate expressions, remove base dir, suffix folders with **
;
let transferPattern = path.join(_paths.apply, filter).toFws;
// test if 'cmscustom/tdi' would be included, then add specifically, because following symlinks doesnt work with glob stars
const tdiIncRegex = /^(config\/)?((\{\w*,?)?cmscustom(,?\w*\})?\/|\*\/)?\*\*/;
const tdiPattern = tdiIncRegex.test(transferPattern) ? transferPattern.replace(tdiIncRegex, `${this.deliveryPack ? 'config/' : ''}cmscustom/tdi/**`) : 'nomatch';
if (!transferPattern)
_error('Invalid glob expression passed!');
else if (this.deliveryPack && transferPattern.startsWith('*'))
transferPattern = `{config,database,${_paths.tdi}/src/database/tdi}/${transferPattern}`;
else if (!this.deliveryPack) {
// for normal deployment to server, set config-dir so no other files in repo are unintentionally transferred
transferPattern = transferPattern.replace(/^config\/|^\//, '');
try { process.chdir('config'); }
catch(e) { _error('No config dir present!'); }
}
this.transferPatterns = [transferPattern, tdiPattern, ...this.giPatterns, '!**/*.crdownload']; // ignore patterns must come last
if (this.deliveryPack && transferPattern.includes(_paths.tdi)) this.transferPatterns.push(`${_paths.tdi}/src/database/create*.sql`); // create(_exists).sql is called by tdi install
// add time parameters to all xopus requests to overcome cache
const ts = new Date().toISOString().replace(/[-:]/g, '').substring(0, 15);
this.replaceStrings = [
[/((xsd|xsl|src|iconsrc)="[^"?]+)"/g, `$1?_=${ts}"`, '**/xopus/xml/*'],
[/(schemaLocation="[^"?]+)"/g, `$1?_=${ts}"`, '**/xopus/xsd/*'],
[/(href="[^"?]+.xsl)"/g, `$1?_=${ts}"`, '**/xopus/xsl/*']
];
// depending on env, enable debug mode and change track functionality
if (this.envDev) {
this.transferPatterns.push('!ldap.xml');
this.replaceStrings.push(
['debug.on=false', 'debug.on=true', '**/util.js'],
['debugMode>false', 'debugMode>true', '**/xopus/xml/*'],
['allowAccept>false', 'allowAccept>true', '**/xopus/xml/*'],
['allowReject>false', 'allowReject>true', '**/xopus/xml/*'],
[/(id="generateSampleText" available=")false/, '$1true', '**/xopus/xml/*']
);
}
else this.replaceStrings.push(['debugMode>true', 'debugMode>false', '**/xopus/xml/*']);
_info(`Branch: ${_git.commitLocal().branch}`);
_info(`Source: ${path.join(process.cwd(), transferPattern)}`);
_info(`Server: ${ftpConfig ? `${ftpConfig.host}:${ftpConfig.port}` : 'local'}`);
_info(`Target: ${this.getRemotePath(transferPattern)}`);
_write();
}
module.exports = {getRemotePath, setServer, prepareForCopy};