Gruntfile.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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. const UglifyJSWebpackPlugin = require("uglifyjs-webpack-plugin");
  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", "exec:generateConfig", "exec:generateNodeIndex", "webpack:node", "webpack:nodeRepl", "chmod:build"]);
  26. grunt.registerTask("test",
  27. "A task which runs all the tests in test/tests.",
  28. ["clean", "exec:generateConfig", "exec:generateNodeIndex", "exec:generateConfig", "exec:tests"]);
  29. grunt.registerTask("docs",
  30. "Compiles documentation in the /docs directory.",
  31. ["clean:docs", "jsdoc", "chmod:docs"]);
  32. grunt.registerTask("prod",
  33. "Creates a production-ready build. Use the --msg flag to add a compile message.",
  34. ["eslint", "clean:prod", "clean:config", "exec:generateConfig", "webpack:web", "inline", "chmod"]);
  35. grunt.registerTask("default",
  36. "Lints the code base",
  37. ["eslint", "exec:repoSize"]);
  38. grunt.registerTask("inline",
  39. "Compiles a production build of CyberChef into a single, portable web page.",
  40. ["exec:generateConfig", "webpack:webInline", "runInliner", "clean:inlineScripts"]);
  41. grunt.registerTask("runInliner", runInliner);
  42. grunt.registerTask("doc", "docs");
  43. grunt.registerTask("tests", "test");
  44. grunt.registerTask("lint", "eslint");
  45. // Load tasks provided by each plugin
  46. grunt.loadNpmTasks("grunt-eslint");
  47. grunt.loadNpmTasks("grunt-webpack");
  48. grunt.loadNpmTasks("grunt-jsdoc");
  49. grunt.loadNpmTasks("grunt-contrib-clean");
  50. grunt.loadNpmTasks("grunt-contrib-copy");
  51. grunt.loadNpmTasks("grunt-contrib-watch");
  52. grunt.loadNpmTasks("grunt-chmod");
  53. grunt.loadNpmTasks("grunt-exec");
  54. grunt.loadNpmTasks("grunt-accessibility");
  55. grunt.loadNpmTasks("grunt-concurrent");
  56. // Project configuration
  57. const compileTime = grunt.template.today("UTC:dd/mm/yyyy HH:MM:ss") + " UTC",
  58. pkg = grunt.file.readJSON("package.json"),
  59. webpackConfig = require("./webpack.config.js"),
  60. BUILD_CONSTANTS = {
  61. COMPILE_TIME: JSON.stringify(compileTime),
  62. COMPILE_MSG: JSON.stringify(grunt.option("compile-msg") || grunt.option("msg") || ""),
  63. PKG_VERSION: JSON.stringify(pkg.version),
  64. ENVIRONMENT_IS_WORKER: function() {
  65. return typeof importScripts === "function";
  66. },
  67. ENVIRONMENT_IS_NODE: function() {
  68. return typeof process === "object" && typeof require === "function";
  69. },
  70. ENVIRONMENT_IS_WEB: function() {
  71. return typeof window === "object";
  72. }
  73. },
  74. moduleEntryPoints = listEntryModules();
  75. /**
  76. * Compiles a production build of CyberChef into a single, portable web page.
  77. */
  78. function runInliner() {
  79. const done = this.async();
  80. Inliner.html({
  81. relativeTo: "build/prod/",
  82. fileContent: grunt.file.read("build/prod/cyberchef.htm"),
  83. images: true,
  84. svgs: true,
  85. scripts: true,
  86. links: true,
  87. strict: true
  88. }, function(error, result) {
  89. if (error) {
  90. if (error instanceof Error) {
  91. done(error);
  92. } else {
  93. done(new Error(error));
  94. }
  95. } else {
  96. grunt.file.write("build/prod/cyberchef.htm", result);
  97. done(true);
  98. }
  99. });
  100. }
  101. /**
  102. * Generates an entry list for all the modules.
  103. */
  104. function listEntryModules() {
  105. const entryModules = {};
  106. glob.sync("./src/core/config/modules/*.mjs").forEach(file => {
  107. const basename = path.basename(file);
  108. if (basename !== "Default.mjs" && basename !== "OpModules.mjs")
  109. entryModules[basename.split(".mjs")[0]] = path.resolve(file);
  110. });
  111. return entryModules;
  112. }
  113. grunt.initConfig({
  114. clean: {
  115. dev: ["build/dev/*"],
  116. prod: ["build/prod/*"],
  117. node: ["build/node/*"],
  118. config: ["src/core/config/OperationConfig.json", "src/core/config/modules/*", "src/code/operations/index.mjs", "src/node/index.mjs", "src/node/config/OperationConfig.json"],
  119. docs: ["docs/*", "!docs/*.conf.json", "!docs/*.ico", "!docs/*.png"],
  120. inlineScripts: ["build/prod/scripts.js"],
  121. },
  122. eslint: {
  123. options: {
  124. configFile: "./.eslintrc.json"
  125. },
  126. configs: ["*.js"],
  127. core: ["src/core/**/*.{js,mjs}", "!src/core/vendor/**/*", "!src/core/operations/legacy/**/*"],
  128. web: ["src/web/**/*.{js,mjs}"],
  129. node: ["src/node/**/*.{js,mjs}"],
  130. tests: ["test/**/*.{js,mjs}"],
  131. },
  132. jsdoc: {
  133. options: {
  134. destination: "docs",
  135. template: "node_modules/ink-docstrap/template",
  136. recurse: true,
  137. readme: "./README.md",
  138. configure: "docs/jsdoc.conf.json"
  139. },
  140. all: {
  141. src: [
  142. "src/**/*.js",
  143. "src/**/*.mjs",
  144. "!src/core/vendor/**/*"
  145. ],
  146. }
  147. },
  148. accessibility: {
  149. options: {
  150. accessibilityLevel: "WCAG2A",
  151. verbose: false,
  152. ignore: [
  153. "WCAG2A.Principle1.Guideline1_3.1_3_1.H42.2"
  154. ]
  155. },
  156. test: {
  157. src: ["build/**/*.html"]
  158. }
  159. },
  160. webpack: {
  161. options: webpackConfig,
  162. web: {
  163. mode: "production",
  164. target: "web",
  165. entry: Object.assign({
  166. main: "./src/web/index.js",
  167. sitemap: "./src/web/static/sitemap.js"
  168. }, moduleEntryPoints),
  169. output: {
  170. path: __dirname + "/build/prod"
  171. },
  172. resolve: {
  173. alias: {
  174. "./config/modules/OpModules": "./config/modules/Default"
  175. }
  176. },
  177. plugins: [
  178. new webpack.DefinePlugin(BUILD_CONSTANTS),
  179. new HtmlWebpackPlugin({
  180. filename: "index.html",
  181. template: "./src/web/html/index.html",
  182. chunks: ["main"],
  183. compileTime: compileTime,
  184. version: pkg.version,
  185. minify: {
  186. removeComments: true,
  187. collapseWhitespace: true,
  188. minifyJS: true,
  189. minifyCSS: true
  190. }
  191. }),
  192. ]
  193. },
  194. webInline: {
  195. mode: "production",
  196. target: "web",
  197. entry: "./src/web/index.js",
  198. output: {
  199. filename: "scripts.js",
  200. path: __dirname + "/build/prod"
  201. },
  202. plugins: [
  203. new webpack.DefinePlugin(Object.assign({}, BUILD_CONSTANTS, {
  204. INLINE: "true"
  205. })),
  206. new HtmlWebpackPlugin({
  207. filename: "cyberchef.htm",
  208. template: "./src/web/html/index.html",
  209. compileTime: compileTime,
  210. version: pkg.version + "s",
  211. inline: true,
  212. minify: {
  213. removeComments: true,
  214. collapseWhitespace: true,
  215. minifyJS: true,
  216. minifyCSS: true
  217. }
  218. }),
  219. ]
  220. },
  221. tests: {
  222. mode: "development",
  223. target: "node",
  224. entry: "./test/index.mjs",
  225. externals: [NodeExternals()],
  226. output: {
  227. filename: "index.js",
  228. path: __dirname + "/build/test"
  229. },
  230. plugins: [
  231. new webpack.DefinePlugin(BUILD_CONSTANTS),
  232. ]
  233. },
  234. node: {
  235. mode: "production",
  236. target: "node",
  237. entry: "./src/node/index.mjs",
  238. externals: [NodeExternals({
  239. whitelist: ["crypto-api/src/crypto-api"]
  240. })],
  241. output: {
  242. filename: "CyberChef.js",
  243. path: __dirname + "/build/node",
  244. library: "CyberChef",
  245. libraryTarget: "commonjs2"
  246. },
  247. plugins: [
  248. new webpack.DefinePlugin(BUILD_CONSTANTS)
  249. ],
  250. optimization: {
  251. minimizer: [
  252. new UglifyJSWebpackPlugin({
  253. cache: true,
  254. parallel: true,
  255. uglifyOptions: {
  256. "keep_fnames": true,
  257. }
  258. })
  259. ]
  260. }
  261. },
  262. nodeRepl: {
  263. mode: "production",
  264. target: "node",
  265. entry: "./src/node/repl-index.mjs",
  266. externals: [NodeExternals({
  267. whitelist: ["crypto-api/src/crypto-api"]
  268. })],
  269. output: {
  270. filename: "CyberChef-repl.js",
  271. path: __dirname + "/build/node",
  272. library: "CyberChef",
  273. libraryTarget: "commonjs2"
  274. },
  275. plugins: [
  276. new webpack.DefinePlugin(BUILD_CONSTANTS)
  277. ],
  278. optimization: {
  279. minimizer: [
  280. new UglifyJSWebpackPlugin({
  281. parallel: true,
  282. cache: true,
  283. uglifyOptions: {
  284. "keep_fnames": true,
  285. }
  286. })
  287. ]
  288. }
  289. }
  290. },
  291. "webpack-dev-server": {
  292. options: {
  293. webpack: webpackConfig,
  294. host: "0.0.0.0",
  295. disableHostCheck: true,
  296. overlay: true,
  297. inline: false,
  298. clientLogLevel: "error",
  299. stats: {
  300. children: false,
  301. chunks: false,
  302. modules: false,
  303. entrypoints: false,
  304. warningsFilter: [
  305. /source-map/,
  306. /dependency is an expression/,
  307. /export 'default'/
  308. ],
  309. }
  310. },
  311. start: {
  312. webpack: {
  313. mode: "development",
  314. target: "web",
  315. entry: Object.assign({
  316. main: "./src/web/index.js"
  317. }, moduleEntryPoints),
  318. resolve: {
  319. alias: {
  320. "./config/modules/OpModules": "./config/modules/Default"
  321. }
  322. },
  323. plugins: [
  324. new webpack.DefinePlugin(BUILD_CONSTANTS),
  325. new HtmlWebpackPlugin({
  326. filename: "index.html",
  327. template: "./src/web/html/index.html",
  328. chunks: ["main"],
  329. compileTime: compileTime,
  330. version: pkg.version,
  331. })
  332. ]
  333. }
  334. }
  335. },
  336. copy: {
  337. ghPages: {
  338. options: {
  339. process: function (content, srcpath) {
  340. // Add Google Analytics code to index.html
  341. if (srcpath.indexOf("index.html") >= 0) {
  342. content = content.replace("</body></html>",
  343. grunt.file.read("src/web/static/ga.html") + "</body></html>");
  344. return grunt.template.process(content, srcpath);
  345. } else {
  346. return content;
  347. }
  348. },
  349. noProcess: ["**", "!**/*.html"]
  350. },
  351. files: [
  352. {
  353. src: "build/prod/index.html",
  354. dest: "build/prod/index.html"
  355. },
  356. {
  357. expand: true,
  358. src: "docs/**",
  359. dest: "build/prod/"
  360. },
  361. ]
  362. }
  363. },
  364. chmod: {
  365. build: {
  366. options: {
  367. mode: "755",
  368. },
  369. src: ["build/**/*", "build/"]
  370. },
  371. docs: {
  372. options: {
  373. mode: "755",
  374. },
  375. src: ["docs/**/*", "docs/"]
  376. }
  377. },
  378. watch: {
  379. config: {
  380. files: ["src/core/operations/**/*", "!src/core/operations/index.mjs"],
  381. tasks: ["exec:generateNodeIndex", "exec:generateConfig"]
  382. }
  383. },
  384. concurrent: {
  385. dev: ["watch:config", "webpack-dev-server:start"],
  386. options: {
  387. logConcurrentOutput: true
  388. }
  389. },
  390. exec: {
  391. repoSize: {
  392. command: [
  393. "git ls-files | wc -l | xargs printf '\n%b\ttracked files\n'",
  394. "du -hs | egrep -o '^[^\t]*' | xargs printf '%b\trepository size\n'"
  395. ].join(";"),
  396. stderr: false
  397. },
  398. cleanGit: {
  399. command: "git gc --prune=now --aggressive"
  400. },
  401. sitemap: {
  402. command: "node build/prod/sitemap.js > build/prod/sitemap.xml"
  403. },
  404. generateConfig: {
  405. command: [
  406. "echo '\n--- Regenerating config files. ---'",
  407. // "node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs",
  408. "mkdir -p src/core/config/modules",
  409. "echo 'export default {};\n' > src/core/config/modules/OpModules.mjs",
  410. "echo '[]\n' > src/core/config/OperationConfig.json",
  411. "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs",
  412. "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs",
  413. "echo '--- Config scripts finished. ---\n'"
  414. ].join(";")
  415. },
  416. generateNodeIndex: {
  417. command: [
  418. "echo '\n--- Regenerating node index ---'",
  419. // Why copy and wipe OperationConfig?
  420. // OperationConfig.json needs to be empty for build to avoid circular dependency on DetectFileType.
  421. // We copy it to node dir so that we can use it as a search corpus in chef.help.
  422. "echo 'Copying OperationConfig.json and wiping original'",
  423. "cp src/core/config/OperationConfig.json src/node/config/OperationConfig.json",
  424. "echo 'export default {};\n' > src/core/config/modules/OpModules.mjs",
  425. "echo '[]\n' > src/core/config/OperationConfig.json",
  426. "echo '\n OperationConfig.json copied to src/node/config. Modules wiped.'",
  427. "node --experimental-modules src/node/config/scripts/generateNodeIndex.mjs",
  428. "echo '--- Node index generated. ---\n'"
  429. ].join(";"),
  430. },
  431. tests: {
  432. command: "node --experimental-modules --no-warnings --no-deprecation test/index.mjs"
  433. }
  434. },
  435. });
  436. };