generator-alfresco-common
Version:
Common code for Alfresco yeoman generator
360 lines (330 loc) • 13.7 kB
JavaScript
;
var assert = require('assert');
var debug = require('debug')('generator-alfresco-common:maven-archetype-generate');
var trace = require('debug')('generator-alfresco-common-trace:maven-archetype-generate');
var extend = require('deep-extend');
var fs = require('fs');
var glob = require('glob');
var path = require('path');
var Buffer = require('safe-buffer').Buffer;
var fileFiltering = require('./maven-archetype-file-filtering');
var pathFiltering = require('./maven-archetype-path-filtering');
var mavenPomModule = require('./maven-pom');
var archetypeMetadataModule = require('./maven-archetype-metadata');
/**
* Given an exploded archetype jar and the metadata xml file for the archetype,
* this code will mimick `mvn archetype:generate` to the extent that we can use
* this module for the Alfresco All-In-One archetype from the Alfresco SDK.
*/
module.exports = function (yo) {
var module = {};
/**
*
* @param {!string} metadataPath
* @param {!string} resourcesPath
* @param {!string} targetPath
* @param {!Object} properties
*/
module.generate = function (metadataPath, resourcesPath, targetPath, properties) {
trace('generate, reading metadata');
const metadata = archetypeMetadataModule(yo.fs.read(metadataPath));
trace('Constructing archetype properties');
const props = extend(metadata.getRequiredProperties(), properties);
props.rootArtifactId = props.artifactId;
trace('Copying top level pom');
const pomPath = copyPom(resourcesPath, targetPath, '', props, yo.fs);
trace('Processing top level FileSets');
const filesets = metadata.getFileSets();
processFileSets(resourcesPath, targetPath, '', filesets, props, yo.fs);
trace('Processing Modules');
const modules = metadata.getModules();
processModules(resourcesPath, targetPath, '', modules, pomPath, props, yo.fs);
trace('generate done');
};
return module;
};
/**
* Copies a pom.xml file from <resourcesPath>/<directory> to <targetPath>/<directory>.
* If there are __placeholders__ that match provided properties in the path, they will
* be expanded in the target folder structure.
*
* @param {!string} resourcesPath
* @param {!string} targetPath
* @param {!string} directory
* @param {!Object} properties
* @param {!Object} yoFs
* @returns {!string} the path to the copied pom.xml
*/
function copyPom (resourcesPath, targetPath, directory, properties, yoFs) {
const fromPath = path.join(resourcesPath, directory, 'pom.xml');
const targetDirectoryPath = path.join(targetPath, directory);
const toPath = path.join(targetDirectoryPath, 'pom.xml');
let copiedFiles = [];
debug('Copying pom from', fromPath, 'to', toPath);
yoFs.copy(fromPath, toPath, {
process:
processFunctionFactory(resourcesPath, directory, '', copiedFiles, properties, true, false),
});
copiedFiles = expandPathPlaceholders(targetPath, copiedFiles, properties, yoFs);
debug('New pom file', path.join(targetPath, copiedFiles[0]));
return path.join(targetPath, copiedFiles[0]);
}
/**
* When a child module is instantiated we need to make sure the parent pom references
* the child.
*
* @param {!string} parentPomPath
* @param {!Object} properties
* @param {!Object} yoFs
*/
function addModuleToParentPom (parentPomPath, properties, yoFs) {
var pomStr = yoFs.read(parentPomPath);
var pomEditor = mavenPomModule(pomStr);
pomEditor.addModule(properties.artifactId);
yoFs.write(parentPomPath, pomEditor.getPOMString());
}
/**
* Given a list of filesets, this will process each one as follows. It arranges for any globbed
* includes to be copied to an appropriate target folder structure and honors any exclusions. It
* also arranges for individual (non-globbed) files to be copied. Once the copies have been
* completed, any __placeholder__ path elements that match provided properties will also be
* resolved.
*
* @param {!string} resourcesPath
* @param {!string} targetPath
* @param {!string} directory
* @param {!Array<{directory: !string, filtered: !boolean, packaged: !boolean}>} filesets
* @param {!Object} properties
* @param {!Object} yoFs
*/
function processFileSets (resourcesPath, targetPath, directory, filesets, properties, yoFs) {
// It appears in mvn archetype:generate, if multiple filesets copy the same file,
// the first file copied wins and subsequent copies of the same file are ignored.
// By running the filesets in reverse order we get the same result without having
// to block copy for existing files, which is not currently supported by
// mem-fs-editor.
filesets.reverse().forEach(fileset => {
debug('Processing FileSet', fileset);
trace('Processing FileSet', fileset);
var relativePath = path.join(directory, fileset.directory);
var fromPath = path.join(resourcesPath, relativePath);
if (!fs.existsSync(fromPath)) {
debug('Skipping FileSet as source is missing');
trace('Skipping FileSet as source is missing');
return;
}
var toPath = path.join(targetPath, relativePath);
var packagedPath = packageToPath(properties.package);
if (fileset.packaged) {
debug('Adding package to path');
trace('Adding package to path');
toPath = path.join(toPath, packageToPath(properties.package));
}
var copiedFiles = [];
var includeGlobs = globsFromFileSet(fromPath, fileset);
var excludeGlobs = globsToExcludeFromFileSet(fromPath, fileset);
trace('Characterized FileSet');
if (includeGlobs) {
includeGlobs.forEach(includeGlob => {
trace('Processing include glob', includeGlob);
debug('Copy glob', includeGlob);
debug('Copying to', toPath);
var includeAndExcludeGlobs = includeGlob;
if (excludeGlobs.length > 0) {
includeAndExcludeGlobs = [includeGlob].concat(excludeGlobs);
debug('Copy glob with exclusion(s)', includeAndExcludeGlobs);
}
try {
trace('About to copy');
yoFs.copy(includeAndExcludeGlobs, toPath, {
process: processFunctionFactory(
resourcesPath, relativePath, packagedPath, copiedFiles,
properties, fileset.filtered, fileset.packaged),
});
trace('Copy complete');
} catch (err) {
// Following is pretty brittle given that mem-fs-editor may change
// this message or how the error is reported.
// TODO(bwavell): contribute option for mem-fs-editor.copy() to allow empties
if (err instanceof assert.AssertionError && err.message.startsWith('Trying to copy from a source that does not exist')) {
debug('It is ok to perform an empty copy');
} else {
throw err;
}
}
});
}
trace('Looking for individual files to copy');
var fromFiles = filesFromFileSet(fromPath, fileset);
trace('Done looking');
if (fromFiles.length > 0) {
trace('Processing include files', fromFiles);
fromFiles.forEach(fromFile => {
var toFile = path.join(toPath, path.basename(fromFile));
debug('Copy from', fromFile);
debug('Copying to', toFile);
trace('About to copy', fromFile);
yoFs.copy(fromFile, toFile, {
process: processFunctionFactory(
resourcesPath, relativePath, packagedPath, copiedFiles,
properties, fileset.filtered, fileset.packaged),
});
trace('Content %s', yoFs.read(toFile));
});
}
trace('Starting path expansion');
copiedFiles = expandPathPlaceholders(targetPath, copiedFiles, properties, yoFs);
trace('Done with path expansion');
debug('In summary, we copied', copiedFiles);
});
}
/**
* Given a list of modules, this accounts for the module directory and updates the artifactId
* property. Then it copies the module pom.xml, any filesets defined in the module and finally
* recuses on any sub-modules.
*
* @param {!string} resourcesPath
* @param {!string} targetPath
* @param {!string} directory
* @param {!Array<{dir: !string, id: !string}>} modules
* @param {!string} parentPomPath
* @param {!Object} properties
* @param {!Object} yoFs
*/
function processModules (resourcesPath, targetPath, directory, modules, parentPomPath, properties, yoFs) {
modules.forEach(module => {
debug('Processing module', module);
var modulePath = path.join(directory, module.dir);
properties.artifactId = pathFiltering.filter(module.dir, properties);
addModuleToParentPom(parentPomPath, properties, yoFs);
var pomPath = copyPom(resourcesPath, targetPath, modulePath, properties, yoFs);
processFileSets(resourcesPath, targetPath, modulePath, module.fileSets, properties, yoFs);
processModules(resourcesPath, targetPath, modulePath, module.modules, pomPath, properties, yoFs);
});
}
/**
* This is a helper that is used to produce a function that is passed to mem-fs-editor.copy() so we can
* capture all of the files that are copied and also so that we can perform content filtering in-place.
*
* @param {!string} basePath is the root directory we are copying from
* @param {!string} relativePath relative path from basePath to where fileset matches
* @param {!string} packagedPath package in path form we can inject when packaged is true
* @param {!Array<string>} list holds files copied relative to basePath
* @param {!Object} properties holds key/value pairs that are replaced when filtered is true
* @param {!boolean} filtered is a flag
* @param {!boolean} packaged is a flag
* @returns {!Function}
*/
function processFunctionFactory (
basePath, relativePath, packagedPath, list, properties, filtered, packaged) {
/**
* @param {!(string|Buffer)} fileContent
* @param {!string} filePath
* @returns {!Buffer}
*/
return function (fileContent, filePath) {
let targetRelativeFilePath = path.relative(basePath, filePath);
trace('Target relative file path: %s', targetRelativeFilePath);
const filesetRelativeFilePath = path.relative(relativePath, targetRelativeFilePath);
trace('Fileset relative file path %s', filesetRelativeFilePath);
if (packaged) {
trace('Repackaging');
const postPackage = path.relative(relativePath, targetRelativeFilePath);
targetRelativeFilePath = path.join(relativePath, packagedPath, postPackage);
trace('Target relative file path with packaging info included', targetRelativeFilePath);
}
debug('Copying %s with size %d', filesetRelativeFilePath, fileContent.length);
list.push(targetRelativeFilePath);
if (filtered) {
trace('Filtering', targetRelativeFilePath);
const strFileContent = (fileContent instanceof Buffer ? fileContent.toString() : fileContent);
const newFileContent = fileFiltering.filter(strFileContent, properties);
trace('Filtered');
return newFileContent;
}
return fileContent;
};
}
/**
* Helper goes through a list of files and expands any __placeholders__ that correspond
* with properties that have been provided.
*
* @param {!string} targetPath the root location we are generating into
* @param {!Array<string>} files a list of files relative to the target path
* @param {!Object} properties list of properties that should be considered for expansion
* @param {!Object} yoFs
*/
function expandPathPlaceholders (targetPath, files, properties, yoFs) {
trace('Expanding path placeholders');
return files.map(file => {
// Don't bother expanding paths without placeholders
if (file.indexOf('__') === -1) {
return file;
}
var newPath = pathFiltering.filter(file, properties);
if (file !== newPath) {
trace('Performing move for path expansion %s', file);
yoFs.move(path.join(targetPath, file), path.join(targetPath, newPath));
trace('Path expansion move completed %s', newPath);
debug('Expanded path placeholder ', newPath);
}
return newPath;
});
}
/**
* The includes in a fileset may be globs or they may be individual files.
* This extracts all of the individual files and makes them absolute.
*
* @param {!string} dirname
* @param {!Array<{includes: Array<string>}>} fileset
* @returns {!Array<string>}
*/
function filesFromFileSet (dirname, fileset) {
if (!fileset.includes || !fileset.includes.length) {
return [];
}
return fileset.includes
.filter(include => !glob.hasMagic(include))
.map(include => path.join(dirname, include));
}
/**
* The includes in a fileset may be globs or they may be individual files.
* This extracts all of the globs and makes them absolute.
*
* @param {!string} dirname
* @param {!Array<{includes: Array<string>}>} fileset
* @returns {!Array<string>}
*/
function globsFromFileSet (dirname, fileset) {
if (!fileset.includes || !fileset.includes.length) {
return [path.join(dirname, '**')];
}
return fileset.includes
.filter(include => glob.hasMagic(include))
.map(include => path.join(dirname, include));
}
/**
* The excludes in a fileset are all treated as globs. This extracts all of
* them and makes them absolute.
*
* @param {!string} dirname
* @param {Array<{excludes: Array<string>}>} fileset
* @returns {Array<string>}
*/
function globsToExcludeFromFileSet (dirname, fileset) {
if (fileset.excludes && fileset.excludes.length) {
return fileset.excludes.map(exclude => '!' + path.join(dirname, exclude));
} else {
return [];
}
}
/**
* Helper used to convert a.standard.package to a/standard/package.
*
* @param {string} pkg
* @returns {string}
*/
function packageToPath (pkg) {
return pkg.replace(/\./g, '/');
}
// vim: autoindent expandtab tabstop=2 shiftwidth=2 softtabstop=2