aemfed
Version:
Upload front-end changes into AEM, refresh relevant resources in the page and get instant notifications from the error.log, all for easier and faster development.
646 lines (645 loc) • 24.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const browser_sync_1 = __importDefault(require("browser-sync"));
const chalk_1 = __importDefault(require("chalk"));
const graceful_fs_1 = __importDefault(require("graceful-fs"));
const path_1 = __importDefault(require("path"));
const request_promise_native_1 = __importDefault(require("request-promise-native"));
const clientlib_tree_1 = require("./clientlib-tree");
const messages = __importStar(require("./messages"));
const style_trees_1 = require("./style-trees");
const instances = {};
let styleTrees;
let config;
function fixJsPath(ref, instance) {
if (ref.jcrPath && typeof ref.line === "number") {
return instance.clientlibTree.jsTrees
.getMappedFile(ref.jcrPath, ref.line)
.then(jsFileMapped => {
if (jsFileMapped) {
ref.jcrPath = jsFileMapped.path;
ref.line = jsFileMapped.line;
messages.setFilePath(ref, config.jcrContentRoots);
}
return ref;
});
}
else {
return Promise.resolve(ref);
}
}
const yuiProfile = {
level: "error",
logger: "com.adobe.granite.ui.clientlibs.impl.YUIScriptProcessor",
getJcrRef: message => {
const sourceRef = messages.getRef(message, /(\d+):(\d+):/, config.jcrContentRoots, {
line: 1,
column: 2,
jcrPath: -1,
filePath: -1
});
return sourceRef;
},
fixJcrRef: (sourceRef, instance) => {
return fixJsPath(sourceRef, instance);
},
postProcess: message => {
return message.replace(/^\n\[ERROR\] /, "");
}
};
const jscompProfile = {
level: "error",
logger: "com.google.javascript.jscomp",
getJcrRef: (message, instance) => {
const sourceRef = messages.getRef(message, /^([^:[\]*'"|\s]+):(\d+):/, config.jcrContentRoots, {
jcrPath: 1,
line: 2,
column: -1,
filePath: -1
});
if (sourceRef) {
const match = /^( *\^)$/gm.exec(message);
if (match) {
sourceRef.column = match[1].length;
}
}
return sourceRef;
},
fixJcrRef: (sourceRef, instance) => {
return fixJsPath(sourceRef, instance);
}
};
const gccProfile = {
level: "error",
logger: "com.adobe.granite.ui.clientlibs.processor.gcc.impl.GCCScriptProcessor"
};
const lessProfile = {
level: "error",
logger: "com.adobe.granite.ui.clientlibs.compiler.less.impl.LessCompilerImpl",
getJcrRef: message => {
const sourceRef = messages.getRef(message, /([^:[\]*'"|\s]+) on line (\d+), column (\d+)/, config.jcrContentRoots);
return sourceRef;
}
};
const htmlLibProfile = {
level: "error",
logger: "com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl",
getJcrRef: message => {
const sourceRef = messages.getRef(message, /Error during assembly of (\/[^:[\]*'"|\s]+\.js)/, config.jcrContentRoots, {
jcrPath: 1,
filePath: -1,
line: -1,
column: -1
});
return sourceRef;
}
};
const cacheLibProfile = {
level: "error",
logger: "com.adobe.granite.ui.clientlibs.impl.LibraryCacheImpl"
};
const acsProfile = {
level: "error",
logger: "com.adobe.acs.commons.rewriter.impl.VersionedClientlibsTransformerFactory"
};
const slingProcessorProfile = {
level: "error",
logger: "org.apache.sling.engine.impl.SlingRequestProcessorImpl"
};
const slingSightlyProfile = {
level: "error",
logger: "org.apache.sling.scripting.sightly"
};
const profiles = [
yuiProfile,
jscompProfile,
gccProfile,
lessProfile,
htmlLibProfile,
cacheLibProfile,
acsProfile,
slingProcessorProfile,
slingSightlyProfile
];
const tracerConfigs = [
{
pattern: /\.(html|jsp)(\?.*|$)/,
profiles: [
yuiProfile,
jscompProfile,
gccProfile,
lessProfile,
htmlLibProfile,
cacheLibProfile,
acsProfile,
slingProcessorProfile,
slingSightlyProfile
]
},
{
pattern: /\.css(\?.*|$)/,
profiles: [lessProfile, htmlLibProfile, cacheLibProfile]
},
{
pattern: /\.js(\?.*|$)/,
profiles: [
yuiProfile,
jscompProfile,
gccProfile,
htmlLibProfile,
cacheLibProfile
]
},
{
pattern: /\.json(\?.*|$)/,
profiles: [slingProcessorProfile]
}
];
function setTracerHeaders(proxyReq, req, configs) {
const url = req.url;
configs.forEach(tracerConfig => {
if (url && tracerConfig.pattern.test(url)) {
const configStrings = tracerConfig.profiles.map(({ logger, level, caller, callerExcludeFilter }) => {
const fragments = [logger];
if (level) {
fragments.push(`level=${level}`);
}
if (caller === true || typeof caller === "number") {
fragments.push(`caller=${caller}`);
}
if (callerExcludeFilter && callerExcludeFilter.length) {
fragments.push(`caller-exclude-filter="${callerExcludeFilter.join("|")}"`);
}
return fragments.join(";");
});
proxyReq.setHeader("Sling-Tracer-Record", "true");
proxyReq.setHeader("Sling-Tracer-Config", configStrings.join(","));
}
});
}
function processTracer(proxyRes, url, instance) {
const header = proxyRes.headers["Sling-Tracer-Request-Id".toLowerCase()];
const slingTracerRequestId = Array.isArray(header)
? header.length
? header[0]
: ""
: header;
if (slingTracerRequestId) {
setTimeout(() => {
const tracerUrl = instance.server +
"/system/console/tracer/" +
slingTracerRequestId +
".json";
request_promise_native_1.default({
json: true,
uri: tracerUrl
}).then((data) => {
if (data && !data.error) {
const trace = data;
generateReport(instance, url, slingTracerRequestId, trace).then(report => {
report.map(line => console.log(line));
});
}
});
}, 100);
}
}
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function generateReport(instance, url, slingTracerRequestId, trace) {
const levelColorMap = {
error: chalk_1.default.red,
warn: chalk_1.default.yellow,
info: chalk_1.default.white,
debug: chalk_1.default.reset,
trace: chalk_1.default.grey
};
const report = [];
const reportMessages = [];
const onlyLines = [];
const onlyPaths = [];
trace.logs.map(traceLog => {
const className = traceLog.logger.substr(traceLog.logger.lastIndexOf(".") + 1);
const coloredLevel = levelColorMap[traceLog.level.toLowerCase()](traceLog.level);
const message = { traceLog };
const profile = profiles.find(p => traceLog.logger === p.logger);
if (profile) {
message.profile = profile;
if (typeof profile.getJcrRef === "function") {
message.sourceRef = profile.getJcrRef(traceLog.message, instance);
if (message.sourceRef) {
const hasPath = message.sourceRef.jcrPath ||
message.sourceRef.absoluteFilePath ||
message.sourceRef.relativeFilePath;
const hasLine = typeof message.sourceRef.line === "number";
if (hasPath && hasLine) {
reportMessages.push(message);
}
else {
if (!hasPath && hasLine) {
if (onlyPaths.length) {
processOnlies();
}
onlyLines.push(message);
}
else if (hasPath && !hasLine) {
onlyPaths.push(message);
}
}
}
}
if (typeof profile.postProcess === "function") {
message.postMessage = profile.postProcess(traceLog.message);
}
}
report.push(chalk_1.default `[${coloredLevel}] {cyan ${className}}: ${message.postMessage ||
traceLog.message}`);
});
if (onlyPaths.length) {
processOnlies();
}
function processOnlies() {
const uniquePathMessages = [];
onlyPaths.forEach(message => {
const onlyPath = message.sourceRef;
if (onlyPath &&
onlyPath.jcrPath &&
!uniquePathMessages.some(up => typeof up.sourceRef !== "undefined" &&
up.sourceRef.jcrPath === onlyPath.jcrPath)) {
uniquePathMessages.push(message);
}
});
if (uniquePathMessages.length) {
uniquePathMessages.forEach((uniquePath, upIndex) => {
const pathRef = uniquePath.sourceRef;
if (upIndex === 0 && onlyLines.length && pathRef) {
onlyLines.forEach((message, index) => {
const lineRef = message.sourceRef;
if (lineRef) {
if (onlyLines.length === index + 1 &&
lineRef &&
lineRef.line === 1 &&
lineRef.column === 0) {
}
else {
lineRef.jcrPath = pathRef.jcrPath;
reportMessages.push(message);
}
}
});
}
else {
reportMessages.push(uniquePath);
}
});
}
onlyLines.splice(0, onlyLines.length);
onlyPaths.splice(0, onlyPaths.length);
}
const requestProgressErrorLogsRaw = [];
const requestProgressErrorLogs = !trace.requestProgressLogs
? []
: trace.requestProgressLogs
.map(message => {
const match = /\d+ LOG SCRIPT ERROR: (.*)/.exec(message);
if (!match) {
return;
}
const postMessage = match[1];
if (/ScriptEvaluationException:$/.test(postMessage)) {
return;
}
const matchingMessages = requestProgressErrorLogsRaw.filter(logMessage => postMessage.endsWith(logMessage));
if (matchingMessages.length === 0) {
requestProgressErrorLogsRaw.push(postMessage);
let ref;
const progressLogPatterns = [
/([^:[\]*'"|\s]+) at line number (\d+) at column number (\d+)/,
/([^:[\]*'"|\s]+)\((\d+),(\d+)\)/
];
progressLogPatterns.some(pattern => {
const patternMatches = pattern.test(message);
if (patternMatches) {
ref = messages.getRef(message, pattern, config.jcrContentRoots);
}
return patternMatches;
});
if (ref) {
reportMessages.push({
postMessage,
sourceRef: ref
});
}
return chalk_1.default `[{red ERROR}] {cyan Sling Request Progress Tracker}: ${postMessage}`;
}
})
.filter(message => typeof message === "string");
const promises = [];
for (const { sourceRef, profile } of reportMessages) {
if (profile && sourceRef) {
if (typeof profile.fixJcrRef === "function") {
promises.push(profile.fixJcrRef(sourceRef, instance));
}
}
}
return Promise.all(promises).then(fixedRefs => {
const uniqueLocalPaths = reportMessages
.map(ref => ref.sourceRef && messages.formatMessage(ref.sourceRef))
.filter((filePath) => typeof filePath === "string")
.filter(onlyUnique);
const finalReport = report.concat(requestProgressErrorLogs, uniqueLocalPaths);
if (finalReport.length > 0) {
finalReport.unshift(chalk_1.default `[{blue ${instance.name}}] Tracer output for [{yellow ${url ||
"[url missing]"}}] (${slingTracerRequestId})`);
}
return finalReport;
});
}
function setSlingTracerBundleInfo(instance) {
const symbolicName = "org.apache.sling.tracer";
const url = instance.server + `/system/console/bundles/${symbolicName}.json`;
return request_promise_native_1.default({
json: true,
uri: url
}).then((data) => {
if (data && data.data && data.data.length > 0) {
const bundleData = data.data[0];
if (Object.keys(bundleData).length > 0 &&
bundleData.symbolicName === symbolicName) {
instance.aemSettings.bundles.tracer = bundleData;
}
}
return instance;
});
}
function setSlingTracerSettings(instance) {
const buster = `${Date.now()}`.slice(-3);
const url = instance.server +
`/system/console/configMgr/org.apache.sling.tracer.internal.LogTracer?post=true&ts=${buster}`;
return request_promise_native_1.default({
json: true,
uri: url
}).then((data) => {
if (data && data.properties && Object.keys(data.properties).length > 0) {
const { properties } = data;
instance.aemSettings.configs.tracer = {
enabled: convert2Boolean(properties.enabled.value),
gzipResponse: convert2Boolean(properties.gzipResponse.value),
recordingCacheDurationInSecs: convert2Int(properties.recordingCacheDurationInSecs.value),
recordingCacheSizeInMB: convert2Int(properties.recordingCacheSizeInMB.value),
recordingCompressionEnabled: convert2Boolean(properties.recordingCompressionEnabled.value),
servletEnabled: convert2Boolean(properties.servletEnabled.value)
};
}
else {
}
return instance;
});
}
function convert2Boolean(value) {
return typeof value === "string" ? value === "true" : value;
}
function convert2Int(value) {
return typeof value === "string" ? parseInt(value, 10) : value;
}
function create(args) {
const bsOptions = {
notify: false,
open: false
};
Object.assign(bsOptions, args.bsOptions);
config = args;
config.jcrContentRoots = config.jcrContentRoots || [
"ui.apps/src/main/content/jcr_root/"
];
args.servers.forEach((server, index) => {
const host = server.substring(server.indexOf("@") + 1);
const name = host;
const instance = {
aemSettings: {
bundles: {},
configs: {}
},
clientlibTree: new clientlib_tree_1.ClientlibTree({ name, server }),
name,
online: true,
port: args.proxyPort + index * 2,
server
};
instances[host] = instance;
});
const hosts = Object.keys(instances);
const swInstanceState = Date.now();
const promisesState = [];
hosts.forEach(host => {
const instance = instances[host];
promisesState.push(setSlingTracerBundleInfo(instance)
.then(() => {
const tracerBundle = instance.aemSettings.bundles.tracer;
if (tracerBundle && tracerBundle.version !== "0.0.2") {
return setSlingTracerSettings(instance).then(() => {
if (instance.aemSettings.configs.tracer) {
const tracerEnabled = instance.aemSettings.configs.tracer.enabled &&
instance.aemSettings.configs.tracer.servletEnabled;
if (!tracerEnabled) {
console.log(chalk_1.default `[{blue ${instance.name}}] {cyan Apache Sling Log Tracer is not enabled, so errors from compiling and minifying Less and Javascript by AEM cannot be shown. To enable it, go to [{yellow ${instance.server}/system/console/configMgr/org.apache.sling.tracer.internal.LogTracer}] and turn on both 'Enabled' and 'Recording Servlet Enabled'. No restart of aemfed needed}.`);
}
}
else {
console.log(chalk_1.default `[{blue ${instance.name}}] {cyan Apache Sling Log Tracer config was not found}.`);
}
return instance;
});
}
else {
const reason = tracerBundle
? `too old (version ${tracerBundle.version})`
: `not installed`;
console.error(chalk_1.default `[{blue ${instance.name}}] {cyan Apache Sling Log Tracer bundle is ${reason}. At least version 1.0.0 is needed for aemfed to intercept AEM error messages. AEM 6.2 and before can install and run 1.0.2 or newer, see the 'Updating Sling Log Tracer' section in the README for instructions}.`);
return instance;
}
})
.catch(err => {
console.error(chalk_1.default `[{blue ${instance.name}}] [{red ERROR}] Something went wrong:`, err);
}));
});
return Promise.all(promisesState).then(() => {
console.log("Get state for all instances: " + (Date.now() - swInstanceState) + " ms");
console.log("");
const swClientlibs = Date.now();
const promisesClientlibs = [];
hosts.forEach(host => {
const instance = instances[host];
promisesClientlibs.push(instance.clientlibTree.init());
});
styleTrees = new style_trees_1.StyleTrees(config.jcrContentRoots);
promisesClientlibs.push(styleTrees.init());
return Promise.all(promisesClientlibs)
.then(() => {
console.log("Build style and clientlib trees: " +
(Date.now() - swClientlibs) +
" ms");
console.log("---------------------------------------");
let promise = Promise.resolve();
hosts.forEach(host => {
const instance = instances[host];
promise = promise.then(() => {
createBsInstancePromise(instance, bsOptions);
});
});
return promise;
})
.catch(reason => {
console.error(`Init rejected: ${reason}`);
});
});
}
exports.create = create;
function createBsInstancePromise(instance, bsOptions) {
return new Promise((resolve, reject) => {
const bs = browser_sync_1.default.create(instance.name);
bsOptions.proxy = {
proxyReq: [
(proxyReq, req, res, proxyOptions) => {
setTracerHeaders(proxyReq, req, tracerConfigs);
}
],
proxyRes: [
(proxyRes, req, res) => {
processTracer(proxyRes, req.url, instance);
}
],
target: instance.server
};
bsOptions.port = instance.port;
bsOptions.ui = {
port: instance.port + 1
};
bs.init(bsOptions, (err, data) => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
}
function reload(host, inputList) {
const instance = instances[host];
const bs = browser_sync_1.default.get(instance.name);
let css = false;
let js = false;
let html = false;
let other = false;
const cssPaths = [];
const csstxtPaths = [];
const jsPaths = [];
const jstxtPaths = [];
const specialPaths = [];
inputList.forEach(absolutePath => {
if (absolutePath) {
if (/\.(css|less|scss)$/.test(absolutePath)) {
cssPaths.push(absolutePath);
}
else if (/\.(js)$/.test(absolutePath)) {
jsPaths.push(absolutePath);
}
else if (/\.(html|jsp)$/.test(absolutePath)) {
html = true;
}
else if (/css\.txt$/.test(absolutePath)) {
csstxtPaths.push(absolutePath);
}
else if (/js\.txt$/.test(absolutePath)) {
jstxtPaths.push(absolutePath);
}
else {
let stat;
try {
stat = graceful_fs_1.default.statSync(absolutePath);
}
catch (err) {
if (err.code === "ENOENT") {
}
else {
console.error("Error:", absolutePath, err);
}
}
if (!stat || stat.isDirectory()) {
specialPaths.push(absolutePath);
}
else {
other = true;
}
}
}
});
css = css || cssPaths.length > 0 || csstxtPaths.length > 0;
js = js || jsPaths.length > 0;
other = other || specialPaths.length > 0;
let sw = Date.now();
const cssRelatedFiles = cssPaths.concat(csstxtPaths);
const cssExt = ".css";
const cssToRefresh = [];
styleTrees.findClientlibs(cssRelatedFiles).forEach(cssFile => {
const cssBase = path_1.default.join(path_1.default.dirname(cssFile), path_1.default.basename(cssFile, cssExt));
const clientLibs = instance.clientlibTree.findClientlibs(cssBase);
clientLibs.forEach(lib => {
if (lib.css && cssToRefresh.indexOf(lib.css) === -1) {
cssToRefresh.push(lib.css);
}
});
});
if (css && !js && !html && !other) {
console.log(chalk_1.default `[{blue ${instance.name}}] Only styling was changed, try to inject`);
bs.reload(cssToRefresh);
}
else {
if (js) {
const relativeJsPaths = [];
jsPaths.forEach(filePath => {
config.jcrContentRoots.forEach(rootDir => {
if (filePath.indexOf(rootDir) === 0) {
const relativeJcrPath = filePath.replace(rootDir, "");
relativeJsPaths.push(relativeJcrPath);
}
});
});
instance.clientlibTree.jsTrees.resetFiles(relativeJsPaths);
}
if (specialPaths.length > 0 || jstxtPaths.length > 0) {
instance.clientlibTree.jsTrees.resetLibs();
}
bs.reload();
if (specialPaths.length > 0) {
console.log(chalk_1.default `[{blue ${instance.name}}] Special paths were changed, so rebuild clientlib tree`);
sw = Date.now();
const promises = [];
instance.clientlibTree = new clientlib_tree_1.ClientlibTree({
name: instance.name,
server: instance.server
});
promises.push(instance.clientlibTree.init());
Promise.all(promises)
.then(() => {
console.log(chalk_1.default `[{blue ${instance.name}}] Rebuild clientlib tree: ${(Date.now() - sw).toString()} ms`);
})
.catch(reason => {
console.log(chalk_1.default `[{blue ${instance.name}}] [{red ERROR}] Rebuild rejected: ${reason}`);
});
}
}
}
exports.reload = reload;