@tangelo/tangelo-configuration-toolkit
Version:
Tangelo Configuration Toolkit is a command-line toolkit which offers support for developing a Tangelo configuration.
313 lines (277 loc) • 12.6 kB
JavaScript
const del = require('del');
const {exec} = require('child_process');
const fs = require('fs-extra');
const globby = require('globby');
const gulp = require('gulp');
const g_print = require('gulp-print');
const path = require('path');
const sass = require('gulp-sass')(require('sass'));
const TclConfig = require('../../lib/tcl-config');
const wws = require('../../lib/worker-with-spinner');
g_print.setLogFunction(filepath => _write(filepath.nostyle));
const compileSass = () => new Promise((resolve, reject) => {
_info('Compile sass to css:');
gulp.src('**/*.scss')
.pipe(sass().on('error', error => reject(error)))
.pipe(g_print.default())
.pipe(gulp.dest('.'))
.on('end', () => {
resolve();
_write();
})
;
});
const cmdExec = command => new Promise(resolve => {
_info(`Executing command (${command}):`);
const cp = exec(command);
const log = msg => {
const line = msg.replace(/\s+$/, ''); // remove excessive whitespace
if (line) console.log(line);
};
cp.stdout.setEncoding('utf8');
cp.stdout.on('data', data => {
if (/Error|Input error/.test(data)) _warn(data);
else log(data);
});
cp.stderr.setEncoding('utf8');
cp.stderr.on('data', data => log(data.replace(/Browserslist: caniuse-lite .*/s, '')));
cp.on('close', () => {
_write();
resolve();
});
});
module.exports = {
init ([fdt, fontoVersion]) {
return new Promise((resolve, reject) => {
_info('Ensure symlink exists:');
const tdiCodebasePath =
_settingsTdi().codebase === 'dual' && fs.existsSync('../project.tcl') ? '/tcl' :
_settingsTdi().codebase === 'dual' ? '/traditional' :
''
;
fs.symlink(path.join(`../../../tdi${tdiCodebasePath}/fonto/packages-shared`), 'packages-shared', 'dir', err => {
if (err && err.code!=='EEXIST') reject(err.code==='EPERM' ? 'Start your console as admin for symlink creation!' : err);
else _write('Done.\n');
resolve();
});
})
.then(() => cmdExec(`${fdt} editor upgrade --version ${fontoVersion} --non-interactive`))
.then(() => [fdt])
;
},
schema ([fdt]) {
return cmdExec(`${fdt} editor schema compile ../schema --overwrite`)
.then(() => {
_info('Updating schema configuration:');
const {rootSchemas} = fs.readJsonSync('../schema/fonto.json');
const fontoManifest = fs.readJsonSync('config/fonto-manifest.json');
let jsonPaths = [];
// remove all schema packages (sx-shell-*) from fonto-manifest.json
Object.keys(fontoManifest.dependencies).filter(k => k.includes('sx-shell-')).forEach(k => {
delete fontoManifest.dependencies[k];
});
// add dependency for sx-module to each package fonto-manifest.json only if sx-module exists
// add schema packages in fonto.json to config/fonto-manifest.json
const sxModuleExists = fs.existsSync('packages/sx-module');
Object.entries(rootSchemas).forEach(([, obj]) => {
const pckPath = 'packages/' + obj.packageName;
if (sxModuleExists) {
const pckFontoManifest = fs.readJsonSync(pckPath + '/fonto-manifest.json', {throws: false}) || {dependencies: {}};
pckFontoManifest.dependencies['sx-module'] = 'packages/sx-module';
fs.outputJsonSync(pckPath + '/fonto-manifest.json', pckFontoManifest, {spaces: 2});
_write(`${pckPath}/fonto-manifest.json`);
}
fontoManifest.dependencies[obj.packageName] = pckPath;
jsonPaths.push(`${pckPath}/src/assets/schemas/${obj.packageName}.json`);
});
fs.outputJsonSync('config/fonto-manifest.json', fontoManifest, {spaces: 2});
_write('config/fonto-manifest.json');
fs.outputFileSync('config/schemaExperienceResolver.js',
`import tanSchemaLocationToSchemaExperienceResolver from 'tan-base/src/schemaExperienceResolver.js';`+
`\n\n`+
`tanSchemaLocationToSchemaExperienceResolver(${JSON.stringify(rootSchemas, null, 2)});`
);
_write('config/schemaExperienceResolver.js');
_write();
_info('Copying schema json files to build folders:');
// deploy-watch copies schema's to server directly, so update local builds to keep integrity
jsonPaths.forEach(jsonPath => {
fs.copySync(jsonPath, 'dev/assets/schemas/' + jsonPath.match(/[^/]*$/)[0]);
fs.copySync(jsonPath, 'dist/assets/schemas/' + jsonPath.match(/[^/]*$/)[0]);
});
_write('Done.\n');
})
.then(() => [fdt])
;
},
elements ([fdt]) {
_info('Elements requiring custom configuration:');
const findElements = ({fdt, rootSchemas, tdiXsdPath}) => { // cannot use variables from outside this function
const {execSync} = require('child_process'), fs = require('fs');
// get all elements defined in default schema
let ignoreElements = [];
fs.readdirSync(tdiXsdPath).forEach(filename => {
const file = fs.readFileSync(tdiXsdPath + filename, {encoding: 'UTF-8'});
ignoreElements.push(...(file.match(/(?<=xs:element name=")[^"]+(?=")/g)||[]));
});
ignoreElements = [...new Set(ignoreElements)]; // remove duplicates
// execute "fdt elements" for each schema package, ignore default elements, and combine results
const schemasPerElement = {};
Object.entries(rootSchemas).forEach(([, obj]) => {
const data = execSync(`${fdt} elements --schema packages/${obj.packageName}/src/assets/schemas/${obj.packageName}.json -C name`, {encoding: 'UTF-8'});
const elements = data.replace(/(^.*?Name\*)|(Printed name\*.*$)/gs, '').split(/\s+/);
const customElements = [...new Set(elements)].filter(e => e && !ignoreElements.includes(e));
customElements.forEach(e => {
if (schemasPerElement[e]) schemasPerElement[e] = [...schemasPerElement[e], obj.packageName];
else schemasPerElement[e] = [obj.packageName];
});
});
const result = [];
Object.entries(schemasPerElement).forEach(([e, s]) => result.push([e, s])); // convert object to array
return result[0] ? result : 'No results.';
};
return wws(
'searching',
findElements,
{
fdt,
rootSchemas: fs.readJsonSync('../schema/fonto.json').rootSchemas,
tdiXsdPath: path.join(_paths.repo, _paths.tdi, 'src/config/cmscustom/[customer]/[project]/schema/xsd/')
},
result => {
if (Array.isArray(result)) result.forEach(([e, s]) => _write(e, s, '\n'));
else _write(result, '\n');
}
)
.then(() => [fdt])
;
},
attributes ([fdt]) {
_info('Attributes requiring custom configuration:');
const findAttributes = ({fdt, rootSchemas, tdiXsdPath}) => { // cannot use variables from outside this function
const {execSync} = require('child_process'), fs = require('fs');
// get all attributes defined in default schema
let ignoreAttributes = [];
fs.readdirSync(tdiXsdPath).forEach(filename => {
const file = fs.readFileSync(tdiXsdPath + filename, {encoding: 'UTF-8'});
ignoreAttributes.push(...(file.match(/(?<=xs:attribute name=")[^"]+(?=")/g)||[]));
});
ignoreAttributes = [...new Set(ignoreAttributes)]; // remove duplicates
// execute "fdt attributes" for each schema package, ignore default attributes, and combine results
const schemasPerAttribute = {};
Object.entries(rootSchemas).forEach(([, obj]) => {
const data = execSync(`${fdt} attributes --schema packages/${obj.packageName}/src/assets/schemas/${obj.packageName}.json`, {encoding: 'UTF-8'});
const attributes = data.replace(/(^.*?Default value\s+)|(\s+Printed name\*.*$)/gs, '').split(/\n\s+/).map(a => a.split(/\s+/)).map(a =>
a[0] + (a[2]==='required' ? ' (required)' : '') + (a[3]==='-' ? ' (no default)' : '')
);
const customAttributes = [...new Set(attributes)].filter(e => e && !ignoreAttributes.includes(e.replace(/ .*/, '')));
customAttributes.forEach(e => {
if (schemasPerAttribute[e]) schemasPerAttribute[e] = [...schemasPerAttribute[e], obj.packageName];
else schemasPerAttribute[e] = [obj.packageName];
});
});
const result = [];
Object.entries(schemasPerAttribute).forEach(([e, s]) => result.push([e, s])); // convert object to array
return result[0] ? result : 'No results.';
};
return wws(
'searching',
findAttributes,
{
fdt,
rootSchemas: fs.readJsonSync('../schema/fonto.json').rootSchemas,
tdiXsdPath: path.join(_paths.repo, _paths.tdi, 'src/config/cmscustom/[customer]/[project]/schema/xsd/')
},
result => {
if (Array.isArray(result)) result.forEach(([e, s]) => _write(e, s, '\n'));
else _write(result, '\n');
}
)
.then(() => [fdt])
;
},
localize ([fdt]) {
const templateFile = 'messages-template-packages.json';
const messagesFile = 'packages/localization/src/messages.nl.json';
const packagesDirs = fs.readdirSync('packages')
.map(p => path.join('packages', p))
.filter(p => fs.lstatSync(p).isDirectory() && !p.match(/sx-shell-|localization/))
.join(' ')
;
_info(`Ensure ${messagesFile} exists:`);
if (!fs.existsSync(messagesFile)) fs.outputJsonSync(messagesFile, []);
_write('Done.\n');
_info('Make temporary copies of json files:');
const attrCfgTmpFiles = globby.sync('packages/*/src/!(messages.|operations-)*.json');
attrCfgTmpFiles.forEach((p, i, a) => {
_write(p);
a[i] = path.join(path.dirname(p), 'operations-' + path.basename(p));
fs.copyFileSync(p, a[i]);
});
_write();
return cmdExec(`${fdt} localization extract ${templateFile} --paths ` + packagesDirs)
.then(() => cmdExec(`${fdt} localization update ${messagesFile} ${templateFile}`))
.then(() => fs.readJson(messagesFile))
.then(data => // compress meta object into string array "refs" and remove "operations-" prefix from paths
data.map(msg => {
msg.refs = msg.meta.map(r => Object.values(r).join(':').replace(/src\/operations-_/, 'src/_'));
delete msg.meta;
return msg;
})
)
.then(data => fs.outputJson(messagesFile, data, {spaces: 2}))
.then(() => {
_info('Cleanup temp files:');
return del([templateFile, ...attrCfgTmpFiles]);
})
.then(() => _write('Done.\n'))
.then(() => [fdt])
;
},
build ([fdt]) {
return compileSass()
.then(() => {
_info('Delete dist/assets folder:');
fs.removeSync('dist/assets');
_write('Done.\n');
})
.then(() => cmdExec(`${fdt} editor build`))
.then(() => {
_info('Read project configuration and create json and css for Fonto:');
new TclConfig('..').outputJson('dist/assets').outputCss('dist');
_write('Done.\n');
})
.then(() => [fdt])
;
},
run ([fdt]) {
return compileSass()
.then(() => {
_info('Read project configuration and create json and css for Fonto:');
new TclConfig('..').outputJson('dev/assets').outputCss('dev');
_write('Done.\n');
})
.then(() => {
_info('Starting watch for tcl and sass files:');
gulp.watch(['**/*.scss', '../*.tcl', 'dev/index.html'])
.on('all', (event, filepath) => {
_write(event + ':', filepath);
if (filepath.endsWith('.scss')) {
gulp.src('**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(gulp.dest('.'))
;
}
else if (filepath.endsWith('.html')) {
new TclConfig('..').modifyFontoHtml('dev');
}
else if (filepath.endsWith('.tcl')) {
new TclConfig('..').outputJson('dev/assets').outputCss('dev');
}
});
_write('Done.\n');
})
.then(() => cmdExec(`${fdt} editor run --port 8888 --write-to-disk`))
;
}
};