Gruntfile.js 15 KB

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