134 lines
3.6 KiB
JavaScript
Executable file
134 lines
3.6 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const Promise = require("bluebird");
|
|
const yargs = require("yargs");
|
|
const fs = require("fs");
|
|
const fsPromises = require("fs").promises;
|
|
const path = require("path");
|
|
const isGlob = require("is-glob");
|
|
const globby = require("globby");
|
|
const splitFilterN = require("split-filter-n");
|
|
const chalk = require("chalk");
|
|
const matchValue = require("match-value");
|
|
const detectSVGScripts = require("detect-svg-scripts");
|
|
|
|
let argv = yargs
|
|
.boolean("errors-only")
|
|
.argv;
|
|
|
|
let failureTypes = [ "externalScriptFile", "inlineScriptTag", "eventHandler" ];
|
|
|
|
Promise.map(argv._, (target) => {
|
|
return Promise.try(() => {
|
|
return fsPromises.stat(target);
|
|
}).then((stats) => {
|
|
if (stats.isDirectory()) {
|
|
// Literal directory
|
|
return {
|
|
type: "glob",
|
|
glob: path.posix.join(target, "**/*.svg")
|
|
};
|
|
} else {
|
|
// Literal single file
|
|
return {
|
|
type: "file",
|
|
path: target
|
|
};
|
|
}
|
|
}).catch({ code: "ENOENT" }, (error) => {
|
|
// Probably meant as a glob pattern
|
|
if (isGlob(target)) {
|
|
return {
|
|
type: "glob",
|
|
glob: target
|
|
};
|
|
} else {
|
|
// TODO: Make this output nicer
|
|
throw error;
|
|
}
|
|
});
|
|
}).then((targets) => {
|
|
if (targets.length === 0) {
|
|
// Default to all SVGs in the current working directory, if the user hasn't specified any explicit paths to scan.
|
|
targets = [{
|
|
type: "glob",
|
|
glob: path.posix.join(process.cwd(), "**/*.svg")
|
|
}];
|
|
}
|
|
|
|
let targetsByType = splitFilterN(targets, [ "file", "glob" ], (target) => target.type);
|
|
|
|
let literalFiles = targetsByType.file.map((target) => target.path);
|
|
let globs = targetsByType.glob.map((target) => target.glob);
|
|
|
|
return Promise.try(() => {
|
|
return globby(globs);
|
|
}).then((globbedPaths) => {
|
|
return literalFiles.concat(globbedPaths);
|
|
});
|
|
}).map((file) => {
|
|
return Promise.try(() => {
|
|
return detectSVGScripts(fs.createReadStream(file));
|
|
}).then((occurrences) => {
|
|
if (occurrences.length > 0) {
|
|
return {
|
|
file: file,
|
|
passed: false,
|
|
occurrences: occurrences
|
|
};
|
|
} else {
|
|
return {
|
|
file: file,
|
|
passed: true,
|
|
occurrences: occurrences
|
|
};
|
|
}
|
|
});
|
|
}).each((result) => {
|
|
if (result.passed === true) {
|
|
if (!argv.errorsOnly) {
|
|
console.log(chalk.green(`${chalk.bold("[ ✔ PASSED ]")} ${result.file}`));
|
|
}
|
|
} else {
|
|
console.log(chalk.red(`${chalk.bold("[ ✘ FAILED ]")} ${result.file}`));
|
|
|
|
let failuresByType = splitFilterN(result.occurrences, failureTypes, (result) => result.type);
|
|
|
|
let foundTypes = failureTypes
|
|
.filter((type) => failuresByType[type].length > 0)
|
|
.map((type) => {
|
|
let failureCount = failuresByType[type].length;
|
|
let suffix = matchValue(type, {
|
|
externalScriptFile: "external script file(s)",
|
|
inlineScriptTag: "inline script tag(s)",
|
|
eventHandler: "inline event handler(s)"
|
|
});
|
|
|
|
if (type === "eventHandler") {
|
|
let attributes = failuresByType.eventHandler.map((item) => item.attribute);
|
|
let uniqueAttributes = Array.from(new Set(attributes));
|
|
|
|
return `${failureCount} ${suffix}: ${uniqueAttributes.join(", ")}`;
|
|
} else {
|
|
return `${failureCount} ${suffix}`;
|
|
}
|
|
});
|
|
|
|
// Indent at the same depth as the filename
|
|
console.log(chalk.gray(` └ Found ${foundTypes.join(", ")}`));
|
|
}
|
|
}).then((allResults) => {
|
|
let failedResults = allResults.filter((result) => result.passed !== true);
|
|
|
|
let totalCount = allResults.length;
|
|
let failedCount = failedResults.length;
|
|
let passedCount = totalCount - failedCount;
|
|
|
|
if (failedCount > 0) {
|
|
process.exitCode = 1;
|
|
}
|
|
|
|
console.log(`Scanned ${totalCount} files, ${chalk.green(`${passedCount} passed`)}, ${chalk.red(`${failedCount} failed`)}`);
|
|
});
|