diff --git a/devTools/scripts/advancedCompiler.js b/devTools/scripts/advancedCompiler.js index 3bfdbe3cd644d639303c97df2c0069d2543c0639..19b0c0ad59bc4bdcbc59c74012e729d468d6bc9b 100644 --- a/devTools/scripts/advancedCompiler.js +++ b/devTools/scripts/advancedCompiler.js @@ -7,10 +7,28 @@ import {execSync} from "child_process"; // @ts-ignore import jetpack from "fs-jetpack"; +import yargs from "yargs"; +import {hideBin} from "yargs/helpers"; + +const args = yargs(hideBin(process.argv)) + .showHelpOnFail(true) + .option('interaction', { + type: 'boolean', + description: 'Used by scripts to let the compiler know that user interaction is not possible.', + default: true, + }) + .option('filename', { + type: 'string', + description: 'The filename to save the compiled HTML file as', + default: "FC_pregmod.html" + }) + .parse(); const batSh = (process.platform === "win32") ? "bat" : "sh"; -console.log(`Using the advanced compiler, run 'simple-compiler.${batSh}' instead to use the simple compiler.`); +if (args.interaction === true) { + console.log(`Using the advanced compiler, run 'simple-compiler.${batSh}' instead to use the simple compiler.`); +} // make sure settings.json exists and has all the required properties execSync("node devTools/scripts/setup.js --settings"); @@ -33,7 +51,10 @@ if (settings.compilerMode === "simple") { } else { if (settings.compilerRunSanityChecks === 1) { try { - execSync("node devTools/scripts/sanityCheck.js", {stdio: "inherit"}); + execSync( + `node devTools/scripts/sanityCheck.js${args.interaction ? "" : " --no-interaction"}`, + {stdio: "inherit"} + ); } catch { console.log("Sanity checks failed! See above for details"); } @@ -62,6 +83,7 @@ if (settings.compilerMode === "simple") { if (settings.compilerFilenamePmodVersion === true) { command += ` --pmodversion`; } + command += ` --filename=${args.filename}` } console.log(`Executing "${command}"`); @@ -70,14 +92,17 @@ execSync(command, {stdio: "inherit"}); if (settings.compilerMode === "advanced" && settings.compilerRunSanityChecks === 2) { try { - execSync("node devTools/scripts/sanityCheck.js", {stdio: "inherit"}); + execSync( + `node devTools/scripts/sanityCheck.js${args.interaction ? "" : " --no-interaction"}`, + {stdio: "inherit"} + ); } catch { console.log("Sanity checks failed! See above for details"); } } console.log(`Run 'setup.${batSh}' to change compiler settings`); -if (settings.compilerWaitOnWindows && process.platform === "win32") { +if (settings.compilerWaitOnWindows && process.platform === "win32" && args.interaction === true) { console.log('Press enter to exit.'); process.stdin.once('data', function() { process.exit(); diff --git a/devTools/scripts/detectChanges.js b/devTools/scripts/detectChanges.js index ea900f691f0e4c952fa8755eeb86371cd97b7f23..600bd1098fd9a341e023688d32fbab0b34f91400 100644 --- a/devTools/scripts/detectChanges.js +++ b/devTools/scripts/detectChanges.js @@ -51,7 +51,13 @@ const settings = jetpack.read("settings.json", "json"); * @class */ class ChangeParser { - async init() { + async init(interactive = true) { + if (interactive === false && settings.fetchUpstreamBranch !== -1 && settings.fetchUpstreamBranch !== 1) { + const batSh = (process.platform === "win32") ? "bat" : "sh"; + console.log("This feature requires fetchUpstreamBranch to be specified."); + console.log(`Please run "setup.${batSh}" and change 'Edit Miscellaneous Settings' -> 'Asking before fetching upstream pregmod-master branch'`); + process.exit(1); + } await this.requestPermission(); if (this.hasPermission === true) { this.fetchOrigin(); diff --git a/devTools/scripts/sanityCheck.js b/devTools/scripts/sanityCheck.js index 222e4cfa06d76122861bad81104bfa503403e0f6..314fa0c1a9932cfce27e355ace81645cdc775c81 100644 --- a/devTools/scripts/sanityCheck.js +++ b/devTools/scripts/sanityCheck.js @@ -23,6 +23,11 @@ const args = yargs(hideBin(process.argv)) description: 'Only check staged files', default: false, }) + .option('interaction', { + type: 'boolean', + description: 'Used by scripts to let the sanity checker know that user interaction is not possible.', + default: true, + }) .parse(); // make sure settings.json exists and has all the required properties @@ -84,7 +89,7 @@ if (args.staged === true) { } } else { // @ts-ignore - await parser.init(); + await parser.init(args.interaction); } if (stagedFiles !== undefined) { diff --git a/devTools/scripts/setup.js b/devTools/scripts/setup.js index 3ed43efa7b21ba9948ea7f1c80afc69bb803c903..8d4efa59adf2a15b0ec9f8e2d1228ee4dbaf2407 100644 --- a/devTools/scripts/setup.js +++ b/devTools/scripts/setup.js @@ -267,6 +267,7 @@ async function compilerSettings() { ) { settings.compilerWaitOnWindows = !settings.compilerWaitOnWindows; } else if (compilerMenuChoice === "Back") { + compilerMenuChoice = 0; return; } @@ -414,6 +415,7 @@ async function sanityCheckSettings() { } else if ( sanityCheckMenuChoice === "Back" ) { + sanityCheckMenuChoice = 0; return; } diff --git a/devTools/scripts/watcher.js b/devTools/scripts/watcher.js new file mode 100644 index 0000000000000000000000000000000000000000..3395f3429702edd74882dff2ddb47fecddeb09ae --- /dev/null +++ b/devTools/scripts/watcher.js @@ -0,0 +1,90 @@ +/** + * @file Watches for changes in the project, when changes occur we run the advanced compiler + */ + +// @ts-ignore +import jetpack from "fs-jetpack"; +import watch from "node-watch"; +import {execSync, spawn} from "child_process"; +import * as path from "path"; + +const batSh = (process.platform === "win32") ? "bat" : "sh"; + +/** @type {import("child_process").ChildProcessWithoutNullStreams} */ +let buildProcess; + +// make sure settings.json exists and has all the required properties +execSync("node devTools/scripts/setup.js --settings"); + +// load settings.json +/** @type {import("./setup.js").Settings} */ +const settings = jetpack.read("settings.json", "json"); + +/** + * Builds FC using the advanced compiler + */ +function build() { + console.log(""); + + if (buildProcess !== undefined) { + buildProcess.kill(); + } + buildProcess = spawn("node", ["devTools/scripts/advancedCompiler.js", "--filename=FC_pregmod.watcher.html", "--no-interaction"], { + stdio: ['inherit', 'inherit', 'inherit'], + }); + + buildProcess.on('exit', function (code) { + if (code === null) { + return; + } else if (code === 0) { + console.log(`Saving changes in "setup.${batSh}" will toggle a rebuild`); + } else { + console.log('Compiler exited with code:', code); + } + }); +} + +const watcher = watch(".", { + recursive: true, + filter(f, skip) { + if ( + f.startsWith("src") || + f.startsWith("js") || + f.startsWith("css") || + f.startsWith("mods") || + f.startsWith("themes") || + f.startsWith("tests") || + f.startsWith("resources") || + f.startsWith("settings.json") || + f.startsWith(`devTools${path.sep}scripts`) || + f.startsWith("gulpfile.js") + ) { + if (f.endsWith("fc-version.js.commitHash.js")) { + return false; + } + return true; + } else if (jetpack.exists(f) === "dir") { + return skip; + } else { + return false; + } + } +}); + +// TODO:@franklygeorge optional launching of a live reloading web server + +watcher.on("change", function(event, filename) { + filename = filename.toString(); + + if (event === "update" && filename && jetpack.exists(filename) === "file") { + console.log(""); + console.log(filename + " changed"); + build(); + } +}); + +console.log("Watching for changes"); +console.log(""); + +// run build when we first startup +build() diff --git a/gulpfile.js b/gulpfile.js index 81811a0e168e6764ff526eea3c2c681a3094ca6c..d63dee4f59cae3a39c5f6e4fae46b33652186531 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -27,6 +27,10 @@ import path from "path"; import os from "os"; import {fileURLToPath} from "url"; +// load json without using "...assert {type: "json"}" which is still in to proposal stage +// https://github.com/tc39/proposal-json-modules +const cfg = jetpack.read(fileURLToPath(new URL('./build.config.json', import.meta.url)), "json"); + // run `npx gulp` to display help const args = yargs(hideBin(process.argv)) .showHelpOnFail(true) @@ -83,6 +87,11 @@ const args = yargs(hideBin(process.argv)) description: 'Adds the current pregmod version number to the output filename', default: false, }) + .option('filename', { + type: 'string', + description: 'The filename to save the compiled HTML file as', + default: cfg.output, + }) // commands should exist as exported gulp tasks .command('html', "Build FC") .command('themes', "Build themes") @@ -91,11 +100,6 @@ const args = yargs(hideBin(process.argv)) .demandCommand() .parse(); -// load json without using "...assert {type: "json"}" which is still in to proposal stage -// https://github.com/tc39/proposal-json-modules - -const cfg = jetpack.read(fileURLToPath(new URL('./build.config.json', import.meta.url)), "json"); - /** * Options used to minify js code using terser * @type {import("terser").MinifyOptions} @@ -497,7 +501,7 @@ function makeModCompilationTask(modName) { */ function moveHTML(cb) { if (jetpack.exists(path.join(cfg.dirs.intermediate, htmlOut)) === "file") { - let finalPath = path.join(cfg.dirs.output, cfg.output); + let finalPath = path.join(cfg.dirs.output, args.filename); let extraString = ""; if (args.epoch) { diff --git a/package.json b/package.json index f9f288f5d4def6e32cf4ead7205a2f278edb1dd4..2f38d3d8c84b3d186a282014c581ad30a3dcf1a9 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "spell:all": "npx cspell --show-context --show-suggestions --gitignore --color \"**/*.{js,md,d.ts}\"", "lint": "node devTools/scripts/eslintChecks.js --changed && node devTools/scripts/typescriptChecks.js --changed", "lint:all": "node devTools/scripts/eslintChecks.js && node devTools/scripts/typescriptChecks.js", - "sanity": "node devTools/scripts/sanityCheck.js" + "sanity": "node devTools/scripts/sanityCheck.js", + "watch": "node devTools/scripts/watcher.js" }, "license": "GPL-3.0-only", "devDependencies": { @@ -43,6 +44,7 @@ "http-server": "^14.1.1", "indefinite": "^2.4.3", "inquirer": "^9.2.15", + "node-watch": "^0.7.4", "strip-ansi": "^7.1.0", "ts-essentials": "^9.1.1", "typescript": "^4.4.0" diff --git a/watcher.bat b/watcher.bat new file mode 100644 index 0000000000000000000000000000000000000000..cad03633d5f8687fcedb68ca34ee5e304890817e --- /dev/null +++ b/watcher.bat @@ -0,0 +1,25 @@ +@ECHO off +:: Free Cities File system watcher - Windows + +:: run dependencyCheck.bat +CALL .\devTools\scripts\dependencyCheck.bat +SET CODE=%ERRORLEVEL% + +IF %CODE% EQU 69 ( + :: if exit code is 69, then we don't have all the dependencies we need + ECHO. + ECHO Dependencies not met. + ECHO. + EXIT /b 0 +) ELSE IF %CODE% EQU 0 ( + :: if exit code is 0, run watcher.js + CALL node devTools\scripts\watcher.js + EXIT /b 0 +) ELSE ( + :: if exit code is not 0, print error message + ECHO. + ECHO dependencyCheck.bat exited with code: %CODE% + ECHO Dependency check failed unexpectedly. + ECHO. + EXIT /b 0 +) diff --git a/watcher.sh b/watcher.sh new file mode 100755 index 0000000000000000000000000000000000000000..97d5f1f5b8f91b84c8bbaf8ea2c9406d86ae5b36 --- /dev/null +++ b/watcher.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Free Cities File system watcher - Unix + +# run dependencyCheck.sh +./devTools/scripts/dependencyCheck.sh +exitCode=$? +# exit code is now stored in $exitCode + +# if exit code is 69, then we don't have all the dependencies we need +if [[ $exitCode -eq 69 ]]; then + echo "Dependencies not met." + echo "" + exit 0 +# if exit code is not 0, print error message +elif [[ $exitCode -ne 0 ]]; then + echo "dependencyCheck.sh exited with code: $exitCode" + echo "" + exit 0 +# if exit code is 0, run watcher.js +else + node devTools/scripts/watcher.js +fi