Gruntfile.js 15 KB

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