Gruntfile.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. "use strict";
  2. const webpack = require("webpack");
  3. const HtmlWebpackPlugin = require("html-webpack-plugin");
  4. const NodeExternals = require("webpack-node-externals");
  5. const Inliner = require("web-resource-inliner");
  6. const glob = require("glob");
  7. const path = require("path");
  8. /**
  9. * Grunt configuration for building the app in various formats.
  10. *
  11. * @author n1474335 [n1474335@gmail.com]
  12. * @copyright Crown Copyright 2017
  13. * @license Apache-2.0
  14. */
  15. module.exports = function (grunt) {
  16. grunt.file.defaultEncoding = "utf8";
  17. grunt.file.preserveBOM = false;
  18. // Tasks
  19. grunt.registerTask("dev",
  20. "A persistent task which creates a development build whenever source files are modified.",
  21. ["clean:dev", "exec:generateConfig", "concurrent:dev"]);
  22. grunt.registerTask("node",
  23. "Compiles CyberChef into a single NodeJS module.",
  24. ["clean:node", "clean:config", "exec:generateConfig", "webpack:node", "chmod:build"]);
  25. grunt.registerTask("test",
  26. "A task which runs all the tests in test/tests.",
  27. ["exec:generateConfig", "exec:tests"]);
  28. grunt.registerTask("docs",
  29. "Compiles documentation in the /docs directory.",
  30. ["clean:docs", "jsdoc", "chmod:docs"]);
  31. grunt.registerTask("prod",
  32. "Creates a production-ready build. Use the --msg flag to add a compile message.",
  33. ["eslint", "clean:prod", "exec:generateConfig", "webpack:web", "inline", "chmod"]);
  34. grunt.registerTask("default",
  35. "Lints the code base",
  36. ["eslint", "exec:repoSize"]);
  37. grunt.registerTask("inline",
  38. "Compiles a production build of CyberChef into a single, portable web page.",
  39. ["exec:generateConfig", "webpack:webInline", "runInliner", "clean:inlineScripts"]);
  40. grunt.registerTask("runInliner", runInliner);
  41. grunt.registerTask("doc", "docs");
  42. grunt.registerTask("tests", "test");
  43. grunt.registerTask("lint", "eslint");
  44. // Load tasks provided by each plugin
  45. grunt.loadNpmTasks("grunt-eslint");
  46. grunt.loadNpmTasks("grunt-webpack");
  47. grunt.loadNpmTasks("grunt-jsdoc");
  48. grunt.loadNpmTasks("grunt-contrib-clean");
  49. grunt.loadNpmTasks("grunt-contrib-copy");
  50. grunt.loadNpmTasks("grunt-contrib-watch");
  51. grunt.loadNpmTasks("grunt-chmod");
  52. grunt.loadNpmTasks("grunt-exec");
  53. grunt.loadNpmTasks("grunt-accessibility");
  54. grunt.loadNpmTasks("grunt-concurrent");
  55. // Project configuration
  56. const compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC",
  57. pkg = grunt.file.readJSON("package.json"),
  58. webpackConfig = require("./webpack.config.js"),
  59. BUILD_CONSTANTS = {
  60. COMPILE_TIME: JSON.stringify(compileTime),
  61. COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
  62. PKG_VERSION: JSON.stringify(pkg.version),
  63. ENVIRONMENT_IS_WORKER: function() {
  64. return typeof importScripts === "function";
  65. },
  66. ENVIRONMENT_IS_NODE: function() {
  67. return typeof process === "object" && typeof require === "function";
  68. },
  69. ENVIRONMENT_IS_WEB: function() {
  70. return typeof window === "object";
  71. }
  72. },
  73. moduleEntryPoints = listEntryModules();
  74. /**
  75. * Compiles a production build of CyberChef into a single, portable web page.
  76. */
  77. function runInliner() {
  78. const done = this.async();
  79. Inliner.html({
  80. relativeTo: "build/prod/",
  81. fileContent: grunt.file.read("build/prod/cyberchef.htm"),
  82. images: true,
  83. svgs: true,
  84. scripts: true,
  85. links: true,
  86. strict: true
  87. }, function(error, result) {
  88. if (error) {
  89. if (error instanceof Error) {
  90. done(error);
  91. } else {
  92. done(new Error(error));
  93. }
  94. } else {
  95. grunt.file.write("build/prod/cyberchef.htm", result);
  96. done(true);
  97. }
  98. });
  99. }
  100. /**
  101. * Generates an entry list for all the modules.
  102. */
  103. function listEntryModules() {
  104. const entryModules = {};
  105. glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
  106. const basename = path.basename(file);
  107. if (basename !== "Default.mjs" && basename !== "OpModules.mjs")
  108. entryModules[basename.split(".mjs")[0]] = path.resolve(file);
  109. });
  110. return entryModules;
  111. }
  112. grunt.initConfig({
  113. clean: {
  114. dev: ["build/dev/*"],
  115. prod: ["build/prod/*"],
  116. node: ["build/node/*"],
  117. config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs"],
  118. docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
  119. inlineScripts: ["build/prod/scripts.js"],
  120. },
  121. eslint: {
  122. options: {
  123. configFile: "./.eslintrc.json"
  124. },
  125. configs: ["Gruntfile.js"],
  126. core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
  127. web: ["src/web/**/*.{js,mjs}"],
  128. node: ["src/node/**/*.{js,mjs}"],
  129. tests: ["test/**/*.{js,mjs}"],
  130. },
  131. jsdoc: {
  132. options: {
  133. destination: "docs",
  134. template: "node_modules/ink-docstrap/template",
  135. recurse: true,
  136. readme: "./README.md",
  137. configure: "docs/jsdoc.conf.json"
  138. },
  139. all: {
  140. src: [
  141. "src/**/*.js",
  142. "src/**/*.mjs",
  143. "!src/core/vendor/**/*"
  144. ],
  145. }
  146. },
  147. accessibility: {
  148. options: {
  149. accessibilityLevel: "WCAG2A",
  150. verbose: false,
  151. ignore: [
  152. "WCAG2A.Principle1.Guideline1_3.1_3_1.H42.2"
  153. ]
  154. },
  155. test: {
  156. src: ["build/**/*.html"]
  157. }
  158. },
  159. webpack: {
  160. options: webpackConfig,
  161. web: {
  162. mode: "production",
  163. target: "web",
  164. entry: Object.assign({
  165. main: "./src/web/index.js",
  166. sitemap: "./src/web/static/sitemap.js"
  167. }, moduleEntryPoints),
  168. output: {
  169. path: __dirname + "/build/prod"
  170. },
  171. resolve: {
  172. alias: {
  173. "./config/modules/OpModules": "./config/modules/Default"
  174. }
  175. },
  176. plugins: [
  177. new webpack.DefinePlugin(BUILD_CONSTANTS),
  178. new HtmlWebpackPlugin({
  179. filename: "index.html",
  180. template: "./src/web/html/index.html",
  181. chunks: ["main"],
  182. compileTime: compileTime,
  183. version: pkg.version,
  184. minify: {
  185. removeComments: true,
  186. collapseWhitespace: true,
  187. minifyJS: true,
  188. minifyCSS: true
  189. }
  190. }),
  191. ]
  192. },
  193. webInline: {
  194. mode: "production",
  195. target: "web",
  196. entry: "./src/web/index.js",
  197. output: {
  198. filename: "scripts.js",
  199. path: __dirname + "/build/prod"
  200. },
  201. plugins: [
  202. new webpack.DefinePlugin(Object.assign({}, BUILD_CONSTANTS, {
  203. INLINE: "true"
  204. })),
  205. new HtmlWebpackPlugin({
  206. filename: "cyberchef.htm",
  207. template: "./src/web/html/index.html",
  208. compileTime: compileTime,
  209. version: pkg.version + "s",
  210. inline: true,
  211. minify: {
  212. removeComments: true,
  213. collapseWhitespace: true,
  214. minifyJS: true,
  215. minifyCSS: true
  216. }
  217. }),
  218. ]
  219. },
  220. tests: {
  221. mode: "development",
  222. target: "node",
  223. entry: "./test/index.mjs",
  224. externals: [NodeExternals()],
  225. output: {
  226. filename: "index.js",
  227. path: __dirname + "/build/test"
  228. },
  229. plugins: [
  230. new webpack.DefinePlugin(BUILD_CONSTANTS)
  231. ]
  232. },
  233. node: {
  234. mode: "production",
  235. target: "node",
  236. entry: "./src/node/index.mjs",
  237. externals: [NodeExternals()],
  238. output: {
  239. filename: "CyberChef.js",
  240. path: __dirname + "/build/node",
  241. library: "CyberChef",
  242. libraryTarget: "commonjs2"
  243. },
  244. plugins: [
  245. new webpack.DefinePlugin(BUILD_CONSTANTS)
  246. ]
  247. }
  248. },
  249. "webpack-dev-server": {
  250. options: {
  251. webpack: webpackConfig,
  252. host: "0.0.0.0",
  253. disableHostCheck: true,
  254. overlay: true,
  255. inline: false,
  256. clientLogLevel: "error",
  257. stats: {
  258. children: false,
  259. chunks: false,
  260. modules: false,
  261. entrypoints: false,
  262. warningsFilter: [/source-map/, /dependency is an expression/],
  263. }
  264. },
  265. start: {
  266. webpack: {
  267. mode: "development",
  268. target: "web",
  269. entry: Object.assign({
  270. main: "./src/web/index.js"
  271. }, moduleEntryPoints),
  272. resolve: {
  273. alias: {
  274. "./config/modules/OpModules": "./config/modules/Default"
  275. }
  276. },
  277. plugins: [
  278. new webpack.DefinePlugin(BUILD_CONSTANTS),
  279. new HtmlWebpackPlugin({
  280. filename: "index.html",
  281. template: "./src/web/html/index.html",
  282. chunks: ["main"],
  283. compileTime: compileTime,
  284. version: pkg.version,
  285. })
  286. ]
  287. }
  288. }
  289. },
  290. copy: {
  291. ghPages: {
  292. options: {
  293. process: function (content, srcpath) {
  294. // Add Google Analytics code to index.html
  295. if (srcpath.indexOf("index.html") >= 0) {
  296. content = content.replace("</body></html>",
  297. grunt.file.read("src/web/static/ga.html") + "</body></html>");
  298. return grunt.template.process(content, srcpath);
  299. } else {
  300. return content;
  301. }
  302. },
  303. noProcess: ["**", "!**/*.html"]
  304. },
  305. files: [
  306. {
  307. src: "build/prod/index.html",
  308. dest: "build/prod/index.html"
  309. },
  310. {
  311. expand: true,
  312. src: "docs/**",
  313. dest: "build/prod/"
  314. },
  315. ]
  316. }
  317. },
  318. chmod: {
  319. build: {
  320. options: {
  321. mode: "755",
  322. },
  323. src: ["build/**/*", "build/"]
  324. },
  325. docs: {
  326. options: {
  327. mode: "755",
  328. },
  329. src: ["docs/**/*", "docs/"]
  330. }
  331. },
  332. watch: {
  333. config: {
  334. files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"],
  335. tasks: ["exec:generateConfig"]
  336. }
  337. },
  338. concurrent: {
  339. dev: ["watch:config", "webpack-dev-server:start"],
  340. options: {
  341. logConcurrentOutput: true
  342. }
  343. },
  344. exec: {
  345. repoSize: {
  346. command: [
  347. "git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'",
  348. "du -hs | egrep -o '^[^\t]*' | xargs printf '%b\trepository size\n'"
  349. ].join(";"),
  350. stderr: false
  351. },
  352. cleanGit: {
  353. command: "git gc --prune=now --aggressive"
  354. },
  355. sitemap: {
  356. command: "node build/prod/sitemap.js > build/prod/sitemap.xml"
  357. },
  358. generateConfig: {
  359. command: [
  360. "echo '\n--- Regenerating config files. ---'",
  361. "mkdir -p src/core/config/modules",
  362. "echo 'export default {};\n' > src/core/config/modules/OpModules.mjs",
  363. "echo '[]\n' > src/core/config/OperationConfig.json",
  364. "node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs",
  365. "node --experimental-modules src/core/config/scripts/generateConfig.mjs",
  366. "echo '--- Config scripts finished. ---\n'"
  367. ].join(";")
  368. },
  369. tests: {
  370. command: "node --experimental-modules test/index.mjs"
  371. }
  372. },
  373. });
  374. };