1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-14 10:41:52 +00:00

Squashed commit of the following:

commit 176343785eea110e529f26027bf864ae04068384
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Fri Dec 8 23:03:51 2023 -0600

    update readme

commit 8c1c3a0c47f5125126cb00d32a48f4f9344a3fb3
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Fri Dec 8 23:00:05 2023 -0600

    fix bugs

commit ea881e68c693a447e0698a3a6e7cfb1f25ccb6cc
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Fri Dec 8 22:46:46 2023 -0600

    expose all tasks with old api

commit fa6d7a3920ff573eadb61425cc077f0e00406164
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Fri Dec 8 21:51:20 2023 -0600

    switch to exported gulp tasks

commit 348b19a0171e65400bcd434cf7b7432f3488a411
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Mon Nov 20 22:55:38 2023 -0600

    parallelize dev build

commit 56de73e2d18d20e5ea7202afc021573a746e5012
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Mon Nov 20 20:44:10 2023 -0600

    use promises in gulpfile

commit 6ab54372482f26acb4769428eefbdc48240a12a1
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Mon Nov 20 20:33:36 2023 -0600

    make java -version print again

commit b0e4cf57bdc404bb3b0e45b7b233d5f7648c800e
Author: EmeraldBlock <yygengjunior@gmail.com>
Date:   Mon Nov 20 20:14:13 2023 -0600

    use promises for gulp tasks
This commit is contained in:
EmeraldBlock 2024-04-24 16:30:01 -05:00
parent b5a7f7736a
commit 39b7e6cb59
14 changed files with 1079 additions and 1024 deletions

View File

@ -57,8 +57,7 @@ and does not intend to provide compatibility for older clients.
### Prerequisites ### Prerequisites
- [ffmpeg](https://www.ffmpeg.org/download.html) - [ffmpeg](https://www.ffmpeg.org/download.html)
- [Node.js 16](https://nodejs.org/en/about/previous-releases) - [Node.js](https://nodejs.org)
(not 17+, see <https://github.com/tobspr-games/shapez.io/issues/1473>)
- [Yarn 1](https://classic.yarnpkg.com/en/docs/install) (not 2, we haven't migrated yet) - [Yarn 1](https://classic.yarnpkg.com/en/docs/install) (not 2, we haven't migrated yet)
- [Java](https://www.oracle.com/java/technologies/downloads/) (or [OpenJDK](https://openjdk.org/)) to run the texture packer - [Java](https://www.oracle.com/java/technologies/downloads/) (or [OpenJDK](https://openjdk.org/)) to run the texture packer
- [cURL](https://curl.se/download.html)[^1] to download the texture packer - [cURL](https://curl.se/download.html)[^1] to download the texture packer

44
gulp/config.js Normal file
View File

@ -0,0 +1,44 @@
import path from "path/posix";
import BrowserSync from "browser-sync";
export const baseDir = path.resolve("..");
export const buildFolder = path.join(baseDir, "build");
export const buildOutputFolder = path.join(baseDir, "build_output");
// Globs for atlas resources
export const rawImageResourcesGlobs = ["../res_raw/atlas.json", "../res_raw/**/*.png"];
// Globs for non-ui resources
export const nonImageResourcesGlobs = ["../res/**/*.woff2", "../res/*.ico", "../res/**/*.webm"];
// Globs for ui resources
export const imageResourcesGlobs = [
"../res/**/*.png",
"../res/**/*.svg",
"../res/**/*.jpg",
"../res/**/*.gif",
];
export const browserSync = BrowserSync.create({});
// Check environment variables
const envVars = [
"SHAPEZ_CLI_SERVER_HOST",
"SHAPEZ_CLI_ALPHA_FTP_USER",
"SHAPEZ_CLI_ALPHA_FTP_PW",
"SHAPEZ_CLI_STAGING_FTP_USER",
"SHAPEZ_CLI_STAGING_FTP_PW",
"SHAPEZ_CLI_LIVE_FTP_USER",
"SHAPEZ_CLI_LIVE_FTP_PW",
"SHAPEZ_CLI_APPLE_ID",
"SHAPEZ_CLI_APPLE_CERT_NAME",
"SHAPEZ_CLI_GITHUB_USER",
"SHAPEZ_CLI_GITHUB_TOKEN",
];
for (let i = 0; i < envVars.length; ++i) {
if (!process.env[envVars[i]]) {
console.warn("Unset environment variable, might cause issues:", envVars[i]);
}
}

View File

@ -1,5 +1,7 @@
import path from "path/posix"; import path from "path/posix";
import gulp from "gulp";
import { getRevision } from "./buildutils.js"; import { getRevision } from "./buildutils.js";
import { buildFolder, browserSync } from "./config.js";
import gulpPostcss from "gulp-postcss"; import gulpPostcss from "gulp-postcss";
import postcssAssets from "postcss-assets"; import postcssAssets from "postcss-assets";
@ -13,17 +15,16 @@ import gulpDartSass from "gulp-dart-sass";
import gulpPlumber from "gulp-plumber"; import gulpPlumber from "gulp-plumber";
import gulpRename from "gulp-rename"; import gulpRename from "gulp-rename";
export default function gulptasksCSS(gulp, buildFolder, browserSync) { // The assets plugin copies the files
// The assets plugin copies the files const commitHash = getRevision();
const commitHash = getRevision(); const postcssAssetsPlugin = postcssAssets({
const postcssAssetsPlugin = postcssAssets({
loadPaths: [path.join(buildFolder, "res", "ui")], loadPaths: [path.join(buildFolder, "res", "ui")],
basePath: buildFolder, basePath: buildFolder,
baseUrl: ".", baseUrl: ".",
}); });
// Postcss configuration // Postcss configuration
const postcssPlugins = prod => { const postcssPlugins = prod => {
const plugins = [postcssAssetsPlugin]; const plugins = [postcssAssetsPlugin];
if (prod) { if (prod) {
plugins.unshift( plugins.unshift(
@ -52,18 +53,18 @@ export default function gulptasksCSS(gulp, buildFolder, browserSync) {
); );
} }
return plugins; return plugins;
}; };
// Performs linting on css // Performs linting on css
gulp.task("css.lint", () => { export function lint() {
return gulp return gulp
.src(["../src/css/**/*.scss"]) .src(["../src/css/**/*.scss"])
.pipe(gulpSassLint({ configFile: ".sasslint.yml" })) .pipe(gulpSassLint({ configFile: ".sasslint.yml" }))
.pipe(gulpSassLint.format()) .pipe(gulpSassLint.format())
.pipe(gulpSassLint.failOnError()); .pipe(gulpSassLint.failOnError());
}); }
function resourcesTask({ isProd }) { function resourcesTask({ isProd }) {
return gulp return gulp
.src("../src/css/main.scss") .src("../src/css/main.scss")
.pipe(gulpPlumber()) .pipe(gulpPlumber())
@ -79,19 +80,17 @@ export default function gulptasksCSS(gulp, buildFolder, browserSync) {
.pipe(gulpPostcss(postcssPlugins(isProd))) .pipe(gulpPostcss(postcssPlugins(isProd)))
.pipe(gulp.dest(buildFolder)) .pipe(gulp.dest(buildFolder))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
export const resources = {
// Builds the css resources // Builds the css resources
gulp.task("css.resources.dev", () => { dev: () => resourcesTask({ isProd: false }),
return resourcesTask({ isProd: false });
});
// Builds the css resources in prod (=minified) // Builds the css resources in prod (=minified)
gulp.task("css.resources.prod", () => { prod: () => resourcesTask({ isProd: true }),
return resourcesTask({ isProd: true }); };
});
function mainTask({ isProd }) { function mainTask({ isProd }) {
return gulp return gulp
.src("../src/css/main.scss") .src("../src/css/main.scss")
.pipe(gulpPlumber()) .pipe(gulpPlumber())
@ -107,18 +106,15 @@ export default function gulptasksCSS(gulp, buildFolder, browserSync) {
.pipe(gulpPostcss(postcssPlugins(isProd))) .pipe(gulpPostcss(postcssPlugins(isProd)))
.pipe(gulp.dest(buildFolder)) .pipe(gulp.dest(buildFolder))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
export const main = {
// Builds the css main // Builds the css main
gulp.task("css.main.dev", () => { dev: () => mainTask({ isProd: false }),
return mainTask({ isProd: false });
});
// Builds the css main in prod (=minified) // Builds the css main in prod (=minified)
gulp.task("css.main.prod", () => { prod: () => mainTask({ isProd: true }),
return mainTask({ isProd: true }); };
});
gulp.task("css.dev", gulp.parallel("css.main.dev", "css.resources.dev")); export const dev = gulp.parallel(main.dev, resources.dev);
gulp.task("css.prod", gulp.parallel("css.main.prod", "css.resources.prod")); export const prod = gulp.parallel(main.prod, resources.prod);
}

View File

@ -1,11 +1,11 @@
import path from "path/posix"; import path from "path/posix";
import fs from "fs"; import fs from "fs/promises";
import gulp from "gulp";
import gulpRename from "gulp-rename"; import gulpRename from "gulp-rename";
import stripJsonComments from "strip-json-comments"; import stripJsonComments from "strip-json-comments";
export default function gulptasksDocs(gulp, buildFolder) { export function convertJsToTs() {
gulp.task("docs.convertJsToTs", () => {
return gulp return gulp
.src(path.join("..", "src", "js", "**", "*.js")) .src(path.join("..", "src", "js", "**", "*.js"))
.pipe( .pipe(
@ -14,10 +14,10 @@ export default function gulptasksDocs(gulp, buildFolder) {
}) })
) )
.pipe(gulp.dest(path.join("..", "tsc_temp"))); .pipe(gulp.dest(path.join("..", "tsc_temp")));
}); }
gulp.task("docs.copyTsconfigForHints", cb => { export async function copyTsconfigForHints() {
const src = fs.readFileSync(path.join("..", "src", "js", "tsconfig.json")).toString(); const src = (await fs.readFile(path.join("..", "src", "js", "tsconfig.json"))).toString();
const baseConfig = JSON.parse(stripJsonComments(src)); const baseConfig = JSON.parse(stripJsonComments(src));
baseConfig.allowJs = false; baseConfig.allowJs = false;
@ -30,9 +30,7 @@ export default function gulptasksDocs(gulp, buildFolder) {
baseConfig.alwaysStrict = false; baseConfig.alwaysStrict = false;
baseConfig.composite = true; baseConfig.composite = true;
baseConfig.outFile = "bundled-ts.js"; baseConfig.outFile = "bundled-ts.js";
fs.writeFileSync(path.join("..", "tsc_temp", "tsconfig.json"), JSON.stringify(baseConfig)); await fs.writeFile(path.join("..", "tsc_temp", "tsconfig.json"), JSON.stringify(baseConfig));
cb();
});
gulp.task("main.prepareDocs", gulp.series("docs.convertJsToTs", "docs.copyTsconfigForHints"));
} }
export const prepareDocs = gulp.series(convertJsToTs, copyTsconfigForHints);

View File

@ -1,23 +1,24 @@
import path from "path/posix"; import path from "path/posix";
import fs from "fs"; import fs from "fs/promises";
import gulp from "gulp";
import { buildFolder } from "./config.js";
import { getRevision, getVersion } from "./buildutils.js"; import { getRevision, getVersion } from "./buildutils.js";
import gulpRename from "gulp-rename"; import gulpRename from "gulp-rename";
import gulpSftp from "gulp-sftp"; import gulpSftp from "gulp-sftp";
export default function gulptasksFTP(gulp, buildFolder) { const commitHash = getRevision();
const commitHash = getRevision();
const additionalFolder = path.join("additional_build_files"); const additionalFolder = path.join("additional_build_files");
const additionalFiles = [ const additionalGlobs = [
path.join(additionalFolder, "*"), path.join(additionalFolder, "*"),
path.join(additionalFolder, "*.*"), path.join(additionalFolder, "*.*"),
path.join(additionalFolder, ".*"), path.join(additionalFolder, ".*"),
]; ];
const credentials = { const credentials = {
alpha: { alpha: {
host: process.env.SHAPEZ_CLI_SERVER_HOST, host: process.env.SHAPEZ_CLI_SERVER_HOST,
user: process.env.SHAPEZ_CLI_ALPHA_FTP_USER, user: process.env.SHAPEZ_CLI_ALPHA_FTP_USER,
@ -33,11 +34,11 @@ export default function gulptasksFTP(gulp, buildFolder) {
user: process.env.SHAPEZ_CLI_LIVE_FTP_USER, user: process.env.SHAPEZ_CLI_LIVE_FTP_USER,
pass: process.env.SHAPEZ_CLI_LIVE_FTP_PW, pass: process.env.SHAPEZ_CLI_LIVE_FTP_PW,
}, },
}; };
// Write the "commit.txt" file // Write the "commit.txt" file
gulp.task("ftp.writeVersion", cb => { export async function writeVersion() {
fs.writeFileSync( await fs.writeFile(
path.join(buildFolder, "version.json"), path.join(buildFolder, "version.json"),
JSON.stringify( JSON.stringify(
{ {
@ -49,20 +50,20 @@ export default function gulptasksFTP(gulp, buildFolder) {
4 4
) )
); );
cb(); }
});
const gameSrcGlobs = [ const gameSrcGlobs = [
path.join(buildFolder, "**/*.*"), path.join(buildFolder, "**/*.*"),
path.join(buildFolder, "**/.*"), path.join(buildFolder, "**/.*"),
path.join(buildFolder, "**/*"), path.join(buildFolder, "**/*"),
path.join(buildFolder, "!**/index.html"), path.join(buildFolder, "!**/index.html"),
]; ];
for (const deployEnv of ["alpha", "prod", "staging"]) { export const upload = Object.fromEntries(
["alpha", "prod", "staging"].map(deployEnv => {
const deployCredentials = credentials[deployEnv]; const deployCredentials = credentials[deployEnv];
gulp.task(`ftp.upload.${deployEnv}.game`, () => { function game() {
return gulp return gulp
.src(gameSrcGlobs, { base: buildFolder }) .src(gameSrcGlobs, { base: buildFolder })
.pipe( .pipe(
@ -71,30 +72,28 @@ export default function gulptasksFTP(gulp, buildFolder) {
}) })
) )
.pipe(gulpSftp(deployCredentials)); .pipe(gulpSftp(deployCredentials));
}); }
gulp.task(`ftp.upload.${deployEnv}.indexHtml`, () => { function indexHtml() {
return gulp return gulp
.src([path.join(buildFolder, "index.html"), path.join(buildFolder, "version.json")], { .src([path.join(buildFolder, "index.html"), path.join(buildFolder, "version.json")], {
base: buildFolder, base: buildFolder,
}) })
.pipe(gulpSftp(deployCredentials)); .pipe(gulpSftp(deployCredentials));
});
gulp.task(`ftp.upload.${deployEnv}.additionalFiles`, () => {
return gulp
.src(additionalFiles, { base: additionalFolder }) //
.pipe(gulpSftp(deployCredentials));
});
gulp.task(
`ftp.upload.${deployEnv}`,
gulp.series(
"ftp.writeVersion",
`ftp.upload.${deployEnv}.game`,
`ftp.upload.${deployEnv}.indexHtml`,
`ftp.upload.${deployEnv}.additionalFiles`
)
);
} }
}
function additionalFiles() {
return gulp.src(additionalGlobs, { base: additionalFolder }).pipe(gulpSftp(deployCredentials));
}
return [
deployEnv,
{
game,
indexHtml,
additionalFiles,
all: gulp.series(writeVersion, game, indexHtml, additionalFiles),
},
];
})
);

View File

@ -1,303 +1,23 @@
import gulp from "gulp"; import gulp from "gulp";
import BrowserSync from "browser-sync"; import * as tasks from "./tasks.js";
const browserSync = BrowserSync.create({});
import path from "path/posix";
import pathNative from "path";
import deleteEmpty from "delete-empty";
import { execSync } from "child_process";
// Load other plugins
import gulpClean from "gulp-clean";
import gulpWebserver from "gulp-webserver";
// Check environment variables
const envVars = [
"SHAPEZ_CLI_SERVER_HOST",
"SHAPEZ_CLI_ALPHA_FTP_USER",
"SHAPEZ_CLI_ALPHA_FTP_PW",
"SHAPEZ_CLI_STAGING_FTP_USER",
"SHAPEZ_CLI_STAGING_FTP_PW",
"SHAPEZ_CLI_LIVE_FTP_USER",
"SHAPEZ_CLI_LIVE_FTP_PW",
"SHAPEZ_CLI_APPLE_ID",
"SHAPEZ_CLI_APPLE_CERT_NAME",
"SHAPEZ_CLI_GITHUB_USER",
"SHAPEZ_CLI_GITHUB_TOKEN",
];
for (let i = 0; i < envVars.length; ++i) {
if (!process.env[envVars[i]]) {
console.warn("Unset environment variable, might cause issues:", envVars[i]);
}
}
const baseDir = path.resolve("..");
const buildFolder = path.join(baseDir, "build");
const buildOuptutFolder = path.join(baseDir, "build_output");
import gulptasksImageResources, * as imgres from "./image-resources.js";
gulptasksImageResources(gulp, buildFolder);
import gulptasksCSS from "./css.js";
gulptasksCSS(gulp, buildFolder, browserSync);
import gulptasksSounds from "./sounds.js";
gulptasksSounds(gulp, buildFolder);
import gulptasksLocalConfig from "./local-config.js";
gulptasksLocalConfig(gulp);
import gulptasksJS from "./js.js";
gulptasksJS(gulp, buildFolder, browserSync);
import gulptasksHTML from "./html.js";
gulptasksHTML(gulp, buildFolder);
import gulptasksFTP from "./ftp.js";
gulptasksFTP(gulp, buildFolder);
import gulptasksDocs from "./docs.js";
gulptasksDocs(gulp, buildFolder);
import gulptasksStandalone from "./standalone.js";
gulptasksStandalone(gulp);
import gulptasksTranslations from "./translations.js";
import { BUILD_VARIANTS } from "./build_variants.js";
gulptasksTranslations(gulp);
///////////////////// BUILD TASKS /////////////////////
// Cleans up everything
gulp.task("utils.cleanBuildFolder", () => {
return gulp.src(buildFolder, { read: false, allowEmpty: true }).pipe(gulpClean({ force: true }));
});
gulp.task("utils.cleanBuildOutputFolder", () => {
return gulp.src(buildOuptutFolder, { read: false, allowEmpty: true }).pipe(gulpClean({ force: true }));
});
gulp.task("utils.cleanBuildTempFolder", () => {
return gulp
.src(path.join("..", "src", "js", "built-temp"), { read: false, allowEmpty: true })
.pipe(gulpClean({ force: true }));
});
gulp.task("utils.cleanImageBuildFolder", () => {
return gulp
.src(path.join("res_built"), { read: false, allowEmpty: true })
.pipe(gulpClean({ force: true }));
});
gulp.task(
"utils.cleanup",
gulp.series("utils.cleanBuildFolder", "utils.cleanImageBuildFolder", "utils.cleanBuildTempFolder")
);
// Requires no uncomitted files
gulp.task("utils.requireCleanWorkingTree", cb => {
let output = execSync("git status -su").toString("ascii").trim().replace(/\r/gi, "").split("\n");
// Filter files which are OK to be untracked
output = output
.map(x => x.replace(/[\r\n]+/gi, ""))
.filter(x => x.indexOf(".local.js") < 0)
.filter(x => x.length > 0);
if (output.length > 0) {
console.error("\n\nYou have unstaged changes, please commit everything first!");
console.error("Unstaged files:");
console.error(output.map(x => "'" + x + "'").join("\n"));
process.exit(1);
}
cb();
});
gulp.task("utils.copyAdditionalBuildFiles", cb => {
const additionalFolder = path.join("additional_build_files");
const additionalSrcGlobs = [
path.join(additionalFolder, "**/*.*"),
path.join(additionalFolder, "**/.*"),
path.join(additionalFolder, "**/*"),
];
return gulp.src(additionalSrcGlobs).pipe(gulp.dest(buildFolder));
});
// Starts a webserver on the built directory (useful for testing prod build)
gulp.task("main.webserver", () => {
return gulp.src(buildFolder).pipe(
gulpWebserver({
livereload: {
enable: true,
},
directoryListing: false,
open: true,
port: 3005,
})
);
});
/** /**
* * @typedef {import("gulp").TaskFunction} TaskFunction
* @param {object} param0 * @typedef {TaskFunction | { [k: string]: Tasks }} Tasks
* @param {keyof typeof BUILD_VARIANTS} param0.version
*/ */
function serveHTML({ version = "web-dev" }) {
browserSync.init({
server: [buildFolder, path.join(baseDir, "mod_examples")],
port: 3005,
ghostMode: {
clicks: false,
scroll: false,
location: false,
forms: false,
},
logLevel: "info",
logPrefix: "BS",
online: false,
xip: false,
notify: false,
reloadDebounce: 100,
reloadOnRestart: true,
watchEvents: ["add", "change"],
});
// Watch .scss files, those trigger a css rebuild /**
gulp.watch(["../src/**/*.scss"], gulp.series("css.dev")); * @param {Tasks} tasks
* @param {string=} prefix
// Watch .html files, those trigger a html rebuild */
gulp.watch("../src/**/*.html", gulp.series("html.dev")); function register(tasks, prefix) {
gulp.watch("./preloader/*.*", gulp.series("html.dev")); if (tasks instanceof Function) {
gulp.task(prefix, tasks);
// Watch translations return;
gulp.watch("../translations/**/*.yaml", gulp.series("translations.convertToJson")); }
for (const [k, v] of Object.entries(tasks)) {
gulp.watch( register(v, prefix == null ? k : `${prefix}.${k}`);
["../res_raw/sounds/sfx/*.mp3", "../res_raw/sounds/sfx/*.wav"], }
gulp.series("sounds.sfx", "sounds.copy")
);
gulp.watch(
["../res_raw/sounds/music/*.mp3", "../res_raw/sounds/music/*.wav"],
gulp.series("sounds.music", "sounds.copy")
);
// Watch resource files and copy them on change
gulp.watch(imgres.rawImageResourcesGlobs, gulp.series("imgres.buildAtlas"));
gulp.watch(imgres.nonImageResourcesGlobs, gulp.series("imgres.copyNonImageResources"));
gulp.watch(imgres.imageResourcesGlobs, gulp.series("imgres.copyImageResources"));
// Watch .atlas files and recompile the atlas on change
gulp.watch("../res_built/atlas/*.atlas", gulp.series("imgres.atlasToJson"));
gulp.watch("../res_built/atlas/*.json", gulp.series("imgres.atlas"));
// Watch the build folder and reload when anything changed
const extensions = ["html", "js", "png", "gif", "jpg", "svg", "mp3", "ico", "woff2", "json"];
gulp.watch(extensions.map(ext => path.join(buildFolder, "**", "*." + ext))).on("change", function (p) {
return gulp
.src(pathNative.resolve(p).replaceAll(pathNative.sep, path.sep))
.pipe(browserSync.reload({ stream: true }));
});
gulp.watch("../src/js/built-temp/*.json").on("change", function (p) {
return gulp
.src(pathNative.resolve(p).replaceAll(pathNative.sep, path.sep))
.pipe(browserSync.reload({ stream: true }));
});
gulp.series("js." + version + ".dev.watch")(() => true);
} }
// Pre and postbuild register(tasks);
gulp.task("step.baseResources", gulp.series("imgres.allOptimized"));
gulp.task("step.deleteEmpty", cb => {
deleteEmpty.sync(buildFolder);
cb();
});
gulp.task("step.postbuild", gulp.series("imgres.cleanupUnusedCssInlineImages", "step.deleteEmpty"));
///////////////////// RUNNABLE TASKS /////////////////////
// Builds everything (dev)
gulp.task(
"build.prepare.dev",
gulp.series(
"utils.cleanup",
"utils.copyAdditionalBuildFiles",
"localConfig.findOrCreate",
"imgres.buildAtlas",
"imgres.atlasToJson",
"imgres.atlas",
"sounds.dev",
"imgres.copyImageResources",
"imgres.copyNonImageResources",
"translations.fullBuild",
"css.dev"
)
);
// Builds everything for every variant
for (const variant in BUILD_VARIANTS) {
const data = BUILD_VARIANTS[variant];
const buildName = "build." + variant;
// build
gulp.task(
buildName + ".code",
gulp.series(
data.standalone ? "sounds.fullbuildHQ" : "sounds.fullbuild",
"translations.fullBuild",
"js." + variant + ".prod"
)
);
gulp.task(buildName + ".resourcesAndCode", gulp.parallel("step.baseResources", buildName + ".code"));
gulp.task(buildName + ".all", gulp.series(buildName + ".resourcesAndCode", "css.prod", "html.prod"));
gulp.task(buildName, gulp.series("utils.cleanup", buildName + ".all", "step.postbuild"));
// Tasks for creating packages. These packages are already distributable, but usually can be further
// wrapped in a different format (an installer for Windows, tarball for Linux, DMG for macOS).
if (data.standalone) {
const packageTasks = [
"win32-x64",
"win32-arm64",
"linux-x64",
"linux-arm64",
"darwin-x64",
"darwin-arm64",
"all",
];
for (const task of packageTasks) {
gulp.task(
`package.${variant}.${task}`,
gulp.series(
"localConfig.findOrCreate",
`build.${variant}`,
"utils.cleanBuildOutputFolder",
`standalone.${variant}.prepare`,
`standalone.${variant}.package.${task}`
)
);
}
}
// serve
gulp.task(
"serve." + variant,
gulp.series("build.prepare.dev", "html.dev", () => serveHTML({ version: variant }))
);
}
// Deploying!
gulp.task(
"deploy.staging",
gulp.series("utils.requireCleanWorkingTree", "build.web-shapezio-beta", "ftp.upload.staging")
);
gulp.task(
"deploy.prod",
gulp.series("utils.requireCleanWorkingTree", "build.web-shapezio", "ftp.upload.prod")
);
// Default task (dev, localhost)
gulp.task("default", gulp.series("serve.standalone-steam"));

View File

@ -2,6 +2,8 @@ import { getRevision } from "./buildutils.js";
import fs from "fs"; import fs from "fs";
import path from "path/posix"; import path from "path/posix";
import crypto from "crypto"; import crypto from "crypto";
import gulp from "gulp";
import { buildFolder } from "./config.js";
import gulpDom from "gulp-dom"; import gulpDom from "gulp-dom";
import gulpHtmlmin from "gulp-htmlmin"; import gulpHtmlmin from "gulp-htmlmin";
@ -20,9 +22,8 @@ function computeIntegrityHash(fullPath, algorithm = "sha256") {
* html.dev * html.dev
* html.prod * html.prod
*/ */
export default function gulptasksHTML(gulp, buildFolder) { const commitHash = getRevision();
const commitHash = getRevision(); async function buildHtml({ integrity = true }) {
async function buildHtml({ integrity = true }) {
return gulp return gulp
.src("../src/html/index.html") .src("../src/html/index.html")
.pipe( .pipe(
@ -62,9 +63,7 @@ export default function gulptasksHTML(gulp, buildFolder) {
style.textContent = loadingCss; style.textContent = loadingCss;
document.head.appendChild(style); document.head.appendChild(style);
let bodyContent = fs let bodyContent = fs.readFileSync(path.join("preloader", "preloader.html")).toString();
.readFileSync(path.join("preloader", "preloader.html"))
.toString();
const bundleScript = document.createElement("script"); const bundleScript = document.createElement("script");
bundleScript.type = "text/javascript"; bundleScript.type = "text/javascript";
@ -97,16 +96,13 @@ export default function gulptasksHTML(gulp, buildFolder) {
.pipe(gulpHtmlBeautify()) .pipe(gulpHtmlBeautify())
.pipe(gulpRename("index.html")) .pipe(gulpRename("index.html"))
.pipe(gulp.dest(buildFolder)); .pipe(gulp.dest(buildFolder));
} }
gulp.task("html.dev", () => { export const dev = () =>
return buildHtml({ buildHtml({
integrity: false, integrity: false,
}); });
}); export const prod = () =>
gulp.task("html.prod", () => { buildHtml({
return buildHtml({
integrity: true, integrity: true,
}); });
});
}

View File

@ -1,12 +1,19 @@
import fs from "fs"; import fs from "fs/promises";
import path from "path/posix"; import path from "path/posix";
import atlasToJson from "./atlas2json.js"; import gulp from "gulp";
import { buildFolder } from "./config.js";
import atlas2Json from "./atlas2json.js";
import { execSync } from "child_process"; import childProcess from "child_process";
const execute = command => import { promisify } from "util";
execSync(command, { const exec = promisify(childProcess.exec);
const execute = command => {
const promise = exec(command, {
encoding: "utf-8", encoding: "utf-8",
}); });
promise.child.stderr.pipe(process.stderr);
return promise;
};
import gulpImagemin from "gulp-imagemin"; import gulpImagemin from "gulp-imagemin";
import imageminJpegtran from "imagemin-jpegtran"; import imageminJpegtran from "imagemin-jpegtran";
@ -15,28 +22,14 @@ import imageminPngquant from "imagemin-pngquant";
import gulpIf from "gulp-if"; import gulpIf from "gulp-if";
import gulpCached from "gulp-cached"; import gulpCached from "gulp-cached";
import gulpClean from "gulp-clean"; import gulpClean from "gulp-clean";
import { nonImageResourcesGlobs, imageResourcesGlobs } from "./config.js";
// Globs for atlas resources
export const rawImageResourcesGlobs = ["../res_raw/atlas.json", "../res_raw/**/*.png"];
// Globs for non-ui resources
export const nonImageResourcesGlobs = ["../res/**/*.woff2", "../res/*.ico", "../res/**/*.webm"];
// Globs for ui resources
export const imageResourcesGlobs = [
"../res/**/*.png",
"../res/**/*.svg",
"../res/**/*.jpg",
"../res/**/*.gif",
];
// Link to download LibGDX runnable-texturepacker.jar // Link to download LibGDX runnable-texturepacker.jar
const runnableTPSource = const runnableTPSource =
"https://libgdx-nightlies.s3.eu-central-1.amazonaws.com/libgdx-runnables/runnable-texturepacker.jar"; "https://libgdx-nightlies.s3.eu-central-1.amazonaws.com/libgdx-runnables/runnable-texturepacker.jar";
export default function gulptasksImageResources(gulp, buildFolder) { // Lossless options
// Lossless options const minifyImagesOptsLossless = () => [
const minifyImagesOptsLossless = () => [
imageminJpegtran({ imageminJpegtran({
progressive: true, progressive: true,
}), }),
@ -48,10 +41,10 @@ export default function gulptasksImageResources(gulp, buildFolder) {
optimizationLevel: 3, optimizationLevel: 3,
colors: 128, colors: 128,
}), }),
]; ];
// Lossy options // Lossy options
const minifyImagesOpts = () => [ const minifyImagesOpts = () => [
gulpImagemin.mozjpeg({ gulpImagemin.mozjpeg({
quality: 80, quality: 80,
maxMemory: 1024 * 1024 * 8, maxMemory: 1024 * 1024 * 8,
@ -71,31 +64,33 @@ export default function gulptasksImageResources(gulp, buildFolder) {
optimizationLevel: 3, optimizationLevel: 3,
colors: 128, colors: 128,
}), }),
]; ];
// Where the resources folder are // Where the resources folder are
const resourcesDestFolder = path.join(buildFolder, "res"); const resourcesDestFolder = path.join(buildFolder, "res");
/** /**
* Determines if an atlas must use lossless compression * Determines if an atlas must use lossless compression
* @param {string} fname * @param {string} fname
*/ */
function fileMustBeLossless(fname) { function fileMustBeLossless(fname) {
return fname.indexOf("lossless") >= 0; return fname.indexOf("lossless") >= 0;
} }
/////////////// ATLAS ///////////////////// /////////////// ATLAS /////////////////////
gulp.task("imgres.buildAtlas", cb => { export async function buildAtlas() {
const config = JSON.stringify("../res_raw/atlas.json"); const config = JSON.stringify("../res_raw/atlas.json");
const source = JSON.stringify("../res_raw"); const source = JSON.stringify("../res_raw");
const dest = JSON.stringify("../res_built/atlas"); const dest = JSON.stringify("../res_built/atlas");
try { try {
// First check whether Java is installed // First check whether Java is installed
execute("java -version"); await execute("java -version");
// Now check and try downloading runnable-texturepacker.jar (22MB) // Now check and try downloading runnable-texturepacker.jar (22MB)
if (!fs.existsSync("./runnable-texturepacker.jar")) { try {
await fs.access("./runnable-texturepacker.jar");
} catch {
const escapedLink = JSON.stringify(runnableTPSource); const escapedLink = JSON.stringify(runnableTPSource);
try { try {
@ -105,26 +100,24 @@ export default function gulptasksImageResources(gulp, buildFolder) {
} }
} }
execute(`java -jar runnable-texturepacker.jar ${source} ${dest} atlas0 ${config}`); await execute(`java -jar runnable-texturepacker.jar ${source} ${dest} atlas0 ${config}`);
} catch { } catch {
console.warn("Building atlas failed. Java not found / unsupported version?"); console.warn("Building atlas failed. Java not found / unsupported version?");
} }
cb(); }
});
// Converts .atlas LibGDX files to JSON // Converts .atlas LibGDX files to JSON
gulp.task("imgres.atlasToJson", cb => { export async function atlasToJson() {
atlasToJson("../res_built/atlas"); atlas2Json("../res_built/atlas");
cb(); }
});
// Copies the atlas to the final destination // Copies the atlas to the final destination
gulp.task("imgres.atlas", () => { export function atlas() {
return gulp.src(["../res_built/atlas/*.png"]).pipe(gulp.dest(resourcesDestFolder)); return gulp.src(["../res_built/atlas/*.png"]).pipe(gulp.dest(resourcesDestFolder));
}); }
// Copies the atlas to the final destination after optimizing it (lossy compression) // Copies the atlas to the final destination after optimizing it (lossy compression)
gulp.task("imgres.atlasOptimized", () => { export function atlasOptimized() {
return gulp return gulp
.src(["../res_built/atlas/*.png"]) .src(["../res_built/atlas/*.png"])
.pipe( .pipe(
@ -135,26 +128,25 @@ export default function gulptasksImageResources(gulp, buildFolder) {
) )
) )
.pipe(gulp.dest(resourcesDestFolder)); .pipe(gulp.dest(resourcesDestFolder));
}); }
//////////////////// RESOURCES ////////////////////// //////////////////// RESOURCES //////////////////////
// Copies all resources which are no ui resources // Copies all resources which are no ui resources
gulp.task("imgres.copyNonImageResources", () => { export function copyNonImageResources() {
return gulp.src(nonImageResourcesGlobs).pipe(gulp.dest(resourcesDestFolder)); return gulp.src(nonImageResourcesGlobs).pipe(gulp.dest(resourcesDestFolder));
}); }
// Copies all ui resources // Copies all ui resources
gulp.task("imgres.copyImageResources", () => { export function copyImageResources() {
return gulp return gulp
.src(imageResourcesGlobs) .src(imageResourcesGlobs)
.pipe(gulpCached("imgres.copyImageResources")) .pipe(gulpCached("imgres.copyImageResources"))
.pipe(gulp.dest(path.join(resourcesDestFolder))); .pipe(gulp.dest(path.join(resourcesDestFolder)));
}); }
// Copies all ui resources and optimizes them // Copies all ui resources and optimizes them
gulp.task("imgres.copyImageResourcesOptimized", () => { export function copyImageResourcesOptimized() {
return gulp return gulp
.src(imageResourcesGlobs) .src(imageResourcesGlobs)
.pipe( .pipe(
@ -165,22 +157,17 @@ export default function gulptasksImageResources(gulp, buildFolder) {
) )
) )
.pipe(gulp.dest(path.join(resourcesDestFolder))); .pipe(gulp.dest(path.join(resourcesDestFolder)));
}); }
// Copies all resources and optimizes them // Copies all resources and optimizes them
gulp.task( export const allOptimized = gulp.parallel(
"imgres.allOptimized", gulp.series(buildAtlas, atlasToJson, atlasOptimized),
gulp.parallel( copyNonImageResources,
"imgres.buildAtlas", copyImageResourcesOptimized
"imgres.atlasToJson", );
"imgres.atlasOptimized",
"imgres.copyNonImageResources",
"imgres.copyImageResourcesOptimized"
)
);
// Cleans up unused images which are instead inline into the css // Cleans up unused images which are instead inline into the css
gulp.task("imgres.cleanupUnusedCssInlineImages", () => { export function cleanupUnusedCssInlineImages() {
return gulp return gulp
.src( .src(
[ [
@ -192,5 +179,4 @@ export default function gulptasksImageResources(gulp, buildFolder) {
{ read: false } { read: false }
) )
.pipe(gulpIf(fname => fname.history[0].indexOf("noinline") < 0, gulpClean({ force: true }))); .pipe(gulpIf(fname => fname.history[0].indexOf("noinline") < 0, gulpClean({ force: true })));
});
} }

View File

@ -1,4 +1,6 @@
import gulp from "gulp";
import { BUILD_VARIANTS } from "./build_variants.js"; import { BUILD_VARIANTS } from "./build_variants.js";
import { buildFolder, browserSync } from "./config.js";
import webpackConfig from "./webpack.config.js"; import webpackConfig from "./webpack.config.js";
import webpackProductionConfig from "./webpack.production.config.js"; import webpackProductionConfig from "./webpack.production.config.js";
@ -15,64 +17,69 @@ import gulpRename from "gulp-rename";
* *
*/ */
export default function gulptasksJS(gulp, buildFolder, browserSync) { //// DEV
//// DEV
for (const variant in BUILD_VARIANTS) { export default Object.fromEntries(
const data = BUILD_VARIANTS[variant]; Object.entries(BUILD_VARIANTS).map(([variant, data]) => {
function watch() {
gulp.task("js." + variant + ".dev.watch", () => { return gulp
gulp.src("../src/js/main.js") .src("../src/js/main.js")
.pipe(webpackStream(webpackConfig)) .pipe(webpackStream(webpackConfig))
.pipe(gulp.dest(buildFolder)) .pipe(gulp.dest(buildFolder))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
}); }
if (!data.standalone) { function build() {
// WEB
gulp.task("js." + variant + ".dev", () => {
return gulp return gulp
.src("../src/js/main.js") .src("../src/js/main.js")
.pipe(webpackStream(webpackConfig)) .pipe(webpackStream(webpackConfig))
.pipe(gulp.dest(buildFolder)); .pipe(gulp.dest(buildFolder));
}); }
gulp.task("js." + variant + ".prod.transpiled", () => { const dev = {
watch,
build,
};
let prod;
if (!data.standalone) {
// WEB
function transpiled() {
return gulp return gulp
.src("../src/js/main.js") .src("../src/js/main.js")
.pipe(webpackStream(webpackProductionConfig)) .pipe(webpackStream(webpackProductionConfig))
.pipe(gulpRename("bundle-transpiled.js")) .pipe(gulpRename("bundle-transpiled.js"))
.pipe(gulp.dest(buildFolder)); .pipe(gulp.dest(buildFolder));
}); }
gulp.task("js." + variant + ".prod.es6", () => { function es6() {
return gulp return gulp
.src("../src/js/main.js") .src("../src/js/main.js")
.pipe(webpackStream(webpackProductionConfig)) .pipe(webpackStream(webpackProductionConfig))
.pipe(gulp.dest(buildFolder)); .pipe(gulp.dest(buildFolder));
}); }
gulp.task(
"js." + variant + ".prod",
prod = {
transpiled,
es6,
build:
// transpiled currently not used // transpiled currently not used
// gulp.parallel("js." + variant + ".prod.transpiled", "js." + variant + ".prod.es6") // gulp.parallel("js." + variant + ".prod.transpiled", "js." + variant + ".prod.es6")
gulp.parallel("js." + variant + ".prod.es6") es6,
); };
} else { } else {
// STANDALONE // STANDALONE
gulp.task("js." + variant + ".dev", () => { function build() {
return gulp
.src("../src/js/main.js")
.pipe(webpackStream(webpackConfig))
.pipe(gulp.dest(buildFolder));
});
gulp.task("js." + variant + ".prod", () => {
return gulp return gulp
.src("../src/js/main.js") .src("../src/js/main.js")
.pipe(webpackStream(webpackProductionConfig)) .pipe(webpackStream(webpackProductionConfig))
.pipe(gulp.dest(buildFolder)); .pipe(gulp.dest(buildFolder));
});
} }
prod = { build };
} }
}
return [variant, { dev, prod }];
})
);

View File

@ -1,14 +1,10 @@
import fs from "fs"; import fs from "fs/promises";
const configTemplatePath = "../src/js/core/config.local.template.js"; const configTemplatePath = "../src/js/core/config.local.template.js";
const configPath = "../src/js/core/config.local.js"; const configPath = "../src/js/core/config.local.js";
export default function gulptasksLocalConfig(gulp) { export async function findOrCreate() {
gulp.task("localConfig.findOrCreate", cb => { try {
if (!fs.existsSync(configPath)) { await fs.copyFile(configTemplatePath, configPath, fs.constants.COPYFILE_EXCL);
fs.copyFileSync(configTemplatePath, configPath); } catch {}
}
cb();
});
} }

View File

@ -1,34 +1,35 @@
import path from "path/posix"; import path from "path/posix";
import audiosprite from "gulp-audiosprite"; import gulp from "gulp";
import { buildFolder } from "./config.js";
import gulpAudiosprite from "gulp-audiosprite";
import gulpClean from "gulp-clean"; import gulpClean from "gulp-clean";
import gulpCache from "gulp-cache"; import gulpCache from "gulp-cache";
import gulpPlumber from "gulp-plumber"; import gulpPlumber from "gulp-plumber";
import gulpFluentFfmpeg from "gulp-fluent-ffmpeg"; import gulpFluentFfmpeg from "gulp-fluent-ffmpeg";
export default function gulptasksSounds(gulp, buildFolder) { // Gather some basic infos
// Gather some basic infos const soundsDir = path.join("..", "res_raw", "sounds");
const soundsDir = path.join("..", "res_raw", "sounds"); const builtSoundsDir = path.join("..", "res_built", "sounds");
const builtSoundsDir = path.join("..", "res_built", "sounds");
gulp.task("sounds.clear", () => { export function clear() {
return gulp.src(builtSoundsDir, { read: false, allowEmpty: true }).pipe(gulpClean({ force: true })); return gulp.src(builtSoundsDir, { read: false, allowEmpty: true }).pipe(gulpClean({ force: true }));
}); }
const filters = ["volume=0.2"]; const filters = ["volume=0.2"];
const fileCache = new gulpCache.Cache({ const fileCache = new gulpCache.Cache({
cacheDirName: "shapezio-precompiled-sounds", cacheDirName: "shapezio-precompiled-sounds",
}); });
function getFileCacheValue(file) { function getFileCacheValue(file) {
const { _isVinyl, base, cwd, contents, history, stat, path } = file; const { _isVinyl, base, cwd, contents, history, stat, path } = file;
const encodedContents = Buffer.from(contents).toString("base64"); const encodedContents = Buffer.from(contents).toString("base64");
return { _isVinyl, base, cwd, contents: encodedContents, history, stat, path }; return { _isVinyl, base, cwd, contents: encodedContents, history, stat, path };
} }
// Encodes the game music // Encodes the game music
gulp.task("sounds.music", () => { export function music() {
return gulp return gulp
.src([path.join(soundsDir, "music", "**", "*.wav"), path.join(soundsDir, "music", "**", "*.mp3")]) .src([path.join(soundsDir, "music", "**", "*.wav"), path.join(soundsDir, "music", "**", "*.mp3")])
.pipe(gulpPlumber()) .pipe(gulpPlumber())
@ -50,10 +51,10 @@ export default function gulptasksSounds(gulp, buildFolder) {
) )
) )
.pipe(gulp.dest(path.join(builtSoundsDir, "music"))); .pipe(gulp.dest(path.join(builtSoundsDir, "music")));
}); }
// Encodes the game music in high quality for the standalone // Encodes the game music in high quality for the standalone
gulp.task("sounds.musicHQ", () => { export function musicHQ() {
return gulp return gulp
.src([path.join(soundsDir, "music", "**", "*.wav"), path.join(soundsDir, "music", "**", "*.mp3")]) .src([path.join(soundsDir, "music", "**", "*.wav"), path.join(soundsDir, "music", "**", "*.mp3")])
.pipe(gulpPlumber()) .pipe(gulpPlumber())
@ -75,15 +76,15 @@ export default function gulptasksSounds(gulp, buildFolder) {
) )
) )
.pipe(gulp.dest(path.join(builtSoundsDir, "music"))); .pipe(gulp.dest(path.join(builtSoundsDir, "music")));
}); }
// Encodes the ui sounds // Encodes the ui sounds
gulp.task("sounds.sfxGenerateSprites", () => { export function sfxGenerateSprites() {
return gulp return gulp
.src([path.join(soundsDir, "sfx", "**", "*.wav"), path.join(soundsDir, "sfx", "**", "*.mp3")]) .src([path.join(soundsDir, "sfx", "**", "*.wav"), path.join(soundsDir, "sfx", "**", "*.mp3")])
.pipe(gulpPlumber()) .pipe(gulpPlumber())
.pipe( .pipe(
audiosprite({ gulpAudiosprite({
format: "howler", format: "howler",
output: "sfx", output: "sfx",
gap: 0.1, gap: 0.1,
@ -91,8 +92,8 @@ export default function gulptasksSounds(gulp, buildFolder) {
}) })
) )
.pipe(gulp.dest(path.join(builtSoundsDir))); .pipe(gulp.dest(path.join(builtSoundsDir)));
}); }
gulp.task("sounds.sfxOptimize", () => { export function sfxOptimize() {
return gulp return gulp
.src([path.join(builtSoundsDir, "sfx.mp3")]) .src([path.join(builtSoundsDir, "sfx.mp3")])
.pipe(gulpPlumber()) .pipe(gulpPlumber())
@ -107,29 +108,25 @@ export default function gulptasksSounds(gulp, buildFolder) {
}) })
) )
.pipe(gulp.dest(path.join(builtSoundsDir))); .pipe(gulp.dest(path.join(builtSoundsDir)));
}); }
gulp.task("sounds.sfxCopyAtlas", () => { export function sfxCopyAtlas() {
return gulp return gulp
.src([path.join(builtSoundsDir, "sfx.json")]) .src([path.join(builtSoundsDir, "sfx.json")])
.pipe(gulp.dest(path.join("..", "src", "js", "built-temp"))); .pipe(gulp.dest(path.join("..", "src", "js", "built-temp")));
}); }
gulp.task( export const sfx = gulp.series(sfxGenerateSprites, sfxOptimize, sfxCopyAtlas);
"sounds.sfx",
gulp.series("sounds.sfxGenerateSprites", "sounds.sfxOptimize", "sounds.sfxCopyAtlas")
);
gulp.task("sounds.copy", () => { export function copy() {
return gulp return gulp
.src(path.join(builtSoundsDir, "**", "*.mp3")) .src(path.join(builtSoundsDir, "**", "*.mp3"))
.pipe(gulpPlumber()) .pipe(gulpPlumber())
.pipe(gulp.dest(path.join(buildFolder, "res", "sounds"))); .pipe(gulp.dest(path.join(buildFolder, "res", "sounds")));
});
gulp.task("sounds.buildall", gulp.parallel("sounds.music", "sounds.sfx"));
gulp.task("sounds.buildallHQ", gulp.parallel("sounds.musicHQ", "sounds.sfx"));
gulp.task("sounds.fullbuild", gulp.series("sounds.clear", "sounds.buildall", "sounds.copy"));
gulp.task("sounds.fullbuildHQ", gulp.series("sounds.clear", "sounds.buildallHQ", "sounds.copy"));
gulp.task("sounds.dev", gulp.series("sounds.buildall", "sounds.copy"));
} }
export const buildall = gulp.parallel(music, sfx);
export const buildallHQ = gulp.parallel(musicHQ, sfx);
export const fullbuild = gulp.series(clear, buildall, copy);
export const fullbuildHQ = gulp.series(clear, buildallHQ, copy);
export const dev = gulp.series(buildall, copy);

View File

@ -2,8 +2,11 @@ import packager from "electron-packager";
import pj from "../electron/package.json" assert { type: "json" }; import pj from "../electron/package.json" assert { type: "json" };
import path from "path/posix"; import path from "path/posix";
import { getVersion } from "./buildutils.js"; import { getVersion } from "./buildutils.js";
import fs from "fs"; import fs from "fs/promises";
import { execSync } from "child_process"; import childProcess from "child_process";
import { promisify } from "util";
const exec = promisify(childProcess.exec);
import gulp from "gulp";
import { BUILD_VARIANTS } from "./build_variants.js"; import { BUILD_VARIANTS } from "./build_variants.js";
import gulpClean from "gulp-clean"; import gulpClean from "gulp-clean";
@ -11,31 +14,30 @@ import gulpClean from "gulp-clean";
const platforms = /** @type {const} */ (["win32", "linux", "darwin"]); const platforms = /** @type {const} */ (["win32", "linux", "darwin"]);
const architectures = /** @type {const} */ (["x64", "arm64"]); const architectures = /** @type {const} */ (["x64", "arm64"]);
export default function gulptasksStandalone(gulp) { export default Object.fromEntries(
for (const variant in BUILD_VARIANTS) { Object.entries(BUILD_VARIANTS)
const variantData = BUILD_VARIANTS[variant]; .filter(([variant, variantData]) => variantData.standalone)
if (!variantData.standalone) { .map(([variant, variantData]) => {
continue;
}
const tempDestDir = path.join("..", "build_output", variant); const tempDestDir = path.join("..", "build_output", variant);
const taskPrefix = "standalone." + variant;
const electronBaseDir = path.join("..", "electron"); const electronBaseDir = path.join("..", "electron");
const tempDestBuildDir = path.join(tempDestDir, "built"); const tempDestBuildDir = path.join(tempDestDir, "built");
gulp.task(taskPrefix + ".prepare.cleanup", () => { function cleanup() {
return gulp.src(tempDestDir, { read: false, allowEmpty: true }).pipe(gulpClean({ force: true })); return gulp
}); .src(tempDestDir, { read: false, allowEmpty: true })
.pipe(gulpClean({ force: true }));
}
gulp.task(taskPrefix + ".prepare.copyPrefab", () => { function copyPrefab() {
const requiredFiles = [ const requiredFiles = [
path.join(electronBaseDir, "node_modules", "**", "*.*"), path.join(electronBaseDir, "node_modules", "**", "*.*"),
path.join(electronBaseDir, "node_modules", "**", ".*"), path.join(electronBaseDir, "node_modules", "**", ".*"),
path.join(electronBaseDir, "favicon*"), path.join(electronBaseDir, "favicon*"),
]; ];
return gulp.src(requiredFiles, { base: electronBaseDir }).pipe(gulp.dest(tempDestBuildDir)); return gulp.src(requiredFiles, { base: electronBaseDir }).pipe(gulp.dest(tempDestBuildDir));
}); }
gulp.task(taskPrefix + ".prepare.writePackageJson", cb => { async function writePackageJson() {
const packageJsonString = JSON.stringify( const packageJsonString = JSON.stringify(
{ {
scripts: { scripts: {
@ -49,47 +51,47 @@ export default function gulptasksStandalone(gulp) {
4 4
); );
fs.writeFileSync(path.join(tempDestBuildDir, "package.json"), packageJsonString); await fs.writeFile(path.join(tempDestBuildDir, "package.json"), packageJsonString);
}
cb(); function minifyCode() {
});
gulp.task(taskPrefix + ".prepare.minifyCode", () => {
return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir)); return gulp.src(path.join(electronBaseDir, "*.js")).pipe(gulp.dest(tempDestBuildDir));
}); }
gulp.task(taskPrefix + ".prepare.copyGamefiles", () => { function copyGamefiles() {
return gulp.src("../build/**/*.*", { base: "../build" }).pipe(gulp.dest(tempDestBuildDir)); return gulp.src("../build/**/*.*", { base: "../build" }).pipe(gulp.dest(tempDestBuildDir));
}); }
gulp.task(taskPrefix + ".killRunningInstances", cb => { async function killRunningInstances() {
try { try {
execSync("taskkill /F /IM shapezio.exe"); await exec("taskkill /F /IM shapezio.exe");
} catch (ex) { } catch (ex) {
console.warn("Failed to kill running instances, maybe none are up."); console.warn("Failed to kill running instances, maybe none are up.");
} }
cb(); }
});
gulp.task( const prepare = {
taskPrefix + ".prepare", cleanup,
gulp.series( copyPrefab,
taskPrefix + ".killRunningInstances", writePackageJson,
taskPrefix + ".prepare.cleanup", minifyCode,
taskPrefix + ".prepare.copyPrefab", copyGamefiles,
taskPrefix + ".prepare.writePackageJson", all: gulp.series(
taskPrefix + ".prepare.minifyCode", killRunningInstances,
taskPrefix + ".prepare.copyGamefiles" cleanup,
) copyPrefab,
); writePackageJson,
minifyCode,
copyGamefiles
),
};
/** /**
* *
* @param {typeof platforms[number] | (typeof platforms[number])[]} platform * @param {typeof platforms[number] | (typeof platforms[number])[]} platform
* @param {typeof architectures[number] | (typeof architectures[number])[]} arch * @param {typeof architectures[number] | (typeof architectures[number])[]} arch
* @param {function():void} cb
*/ */
async function packageStandalone(platform, arch, cb) { async function packageStandalone(platform, arch) {
const appPaths = await packager({ const appPaths = await packager({
dir: tempDestBuildDir, dir: tempDestBuildDir,
appCopyright: "tobspr Games", appCopyright: "tobspr Games",
@ -108,24 +110,36 @@ export default function gulptasksStandalone(gulp) {
}); });
console.log("Packages created:", appPaths); console.log("Packages created:", appPaths);
for (const appPath of appPaths) { await Promise.all(
fs.writeFileSync(path.join(appPath, "LICENSE"), fs.readFileSync(path.join("..", "LICENSE"))); appPaths.map(async appPath => {
} await fs.writeFile(
path.join(appPath, "LICENSE"),
cb(); await fs.readFile(path.join("..", "LICENSE"))
} );
})
for (const platform of platforms) {
for (const arch of architectures) {
gulp.task(taskPrefix + `.package.${platform}-${arch}`, cb =>
packageStandalone(platform, arch, cb)
); );
} }
}
const pack = {
...Object.fromEntries(
platforms.flatMap(platform =>
architectures.map(arch => [
`${platform}-${arch}`,
() => packageStandalone(platform, arch),
])
)
),
// TODO: Review this hack forced by readonly types // TODO: Review this hack forced by readonly types
gulp.task(taskPrefix + ".package.all", cb => all: () => packageStandalone([...platforms], [...architectures]),
packageStandalone([...platforms], [...architectures], cb) };
);
} return [
} variant,
{
killRunningInstances,
prepare,
package: pack,
},
];
})
);

304
gulp/tasks.js Normal file
View File

@ -0,0 +1,304 @@
import gulp from "gulp";
import path from "path/posix";
import pathNative from "path";
import delEmpty from "delete-empty";
import childProcess from "child_process";
import { promisify } from "util";
const exec = promisify(childProcess.exec);
import { BUILD_VARIANTS } from "./build_variants.js";
import {
baseDir,
buildFolder,
buildOutputFolder,
browserSync,
rawImageResourcesGlobs,
nonImageResourcesGlobs,
imageResourcesGlobs,
} from "./config.js";
// Load other plugins
import gulpClean from "gulp-clean";
import gulpWebserver from "gulp-webserver";
import * as imgres from "./image-resources.js";
import * as css from "./css.js";
import * as sounds from "./sounds.js";
import * as localConfig from "./local-config.js";
import js from "./js.js";
import * as html from "./html.js";
import * as ftp from "./ftp.js";
import * as docs from "./docs.js";
import standalone from "./standalone.js";
import * as translations from "./translations.js";
export { imgres, css, sounds, localConfig, js, html, ftp, docs, standalone, translations };
///////////////////// BUILD TASKS /////////////////////
// Cleans up everything
function cleanBuildFolder() {
return gulp.src(buildFolder, { read: false, allowEmpty: true }).pipe(gulpClean({ force: true }));
}
function cleanBuildOutputFolder() {
return gulp.src(buildOutputFolder, { read: false, allowEmpty: true }).pipe(gulpClean({ force: true }));
}
function cleanBuildTempFolder() {
return gulp
.src(path.join("..", "src", "js", "built-temp"), { read: false, allowEmpty: true })
.pipe(gulpClean({ force: true }));
}
function cleanImageBuildFolder() {
return gulp
.src(path.join("res_built"), { read: false, allowEmpty: true })
.pipe(gulpClean({ force: true }));
}
const cleanup = gulp.series(cleanBuildFolder, cleanImageBuildFolder, cleanBuildTempFolder);
// Requires no uncomitted files
async function requireCleanWorkingTree() {
let output = (await exec("git status -su", { encoding: "buffer" })).stdout
.toString("ascii")
.trim()
.replace(/\r/gi, "")
.split("\n");
// Filter files which are OK to be untracked
output = output
.map(x => x.replace(/[\r\n]+/gi, ""))
.filter(x => x.indexOf(".local.js") < 0)
.filter(x => x.length > 0);
if (output.length > 0) {
console.error("\n\nYou have unstaged changes, please commit everything first!");
console.error("Unstaged files:");
console.error(output.map(x => "'" + x + "'").join("\n"));
process.exit(1);
}
}
function copyAdditionalBuildFiles() {
const additionalFolder = path.join("additional_build_files");
const additionalSrcGlobs = [
path.join(additionalFolder, "**/*.*"),
path.join(additionalFolder, "**/.*"),
path.join(additionalFolder, "**/*"),
];
return gulp.src(additionalSrcGlobs).pipe(gulp.dest(buildFolder));
}
export const utils = {
cleanBuildFolder,
cleanBuildOutputFolder,
cleanBuildTempFolder,
cleanImageBuildFolder,
cleanup,
requireCleanWorkingTree,
copyAdditionalBuildFiles,
};
// Starts a webserver on the built directory (useful for testing prod build)
function webserver() {
return gulp.src(buildFolder).pipe(
gulpWebserver({
livereload: {
enable: true,
},
directoryListing: false,
open: true,
port: 3005,
})
);
}
/**
*
* @param {object} param0
* @param {keyof typeof BUILD_VARIANTS} param0.version
*/
function serveHTML({ version = "web-dev" }) {
browserSync.init({
server: [buildFolder, path.join(baseDir, "mod_examples")],
port: 3005,
ghostMode: {
clicks: false,
scroll: false,
location: false,
forms: false,
},
logLevel: "info",
logPrefix: "BS",
online: false,
xip: false,
notify: false,
reloadDebounce: 100,
reloadOnRestart: true,
watchEvents: ["add", "change"],
});
// Watch .scss files, those trigger a css rebuild
gulp.watch(["../src/**/*.scss"], css.dev);
// Watch .html files, those trigger a html rebuild
gulp.watch("../src/**/*.html", html.dev);
gulp.watch("./preloader/*.*", html.dev);
// Watch translations
gulp.watch("../translations/**/*.yaml", translations.convertToJson);
gulp.watch(
["../res_raw/sounds/sfx/*.mp3", "../res_raw/sounds/sfx/*.wav"],
gulp.series(sounds.sfx, sounds.copy)
);
gulp.watch(
["../res_raw/sounds/music/*.mp3", "../res_raw/sounds/music/*.wav"],
gulp.series(sounds.music, sounds.copy)
);
// Watch resource files and copy them on change
gulp.watch(rawImageResourcesGlobs, imgres.buildAtlas);
gulp.watch(nonImageResourcesGlobs, imgres.copyNonImageResources);
gulp.watch(imageResourcesGlobs, imgres.copyImageResources);
// Watch .atlas files and recompile the atlas on change
gulp.watch("../res_built/atlas/*.atlas", imgres.atlasToJson);
gulp.watch("../res_built/atlas/*.json", imgres.atlas);
// Watch the build folder and reload when anything changed
const extensions = ["html", "js", "png", "gif", "jpg", "svg", "mp3", "ico", "woff2", "json"];
gulp.watch(extensions.map(ext => path.join(buildFolder, "**", "*." + ext))).on("change", p =>
gulp
.src(pathNative.resolve(p).replaceAll(pathNative.sep, path.sep))
.pipe(browserSync.reload({ stream: true }))
);
gulp.watch("../src/js/built-temp/*.json").on("change", p =>
gulp
.src(pathNative.resolve(p).replaceAll(pathNative.sep, path.sep))
.pipe(browserSync.reload({ stream: true }))
);
gulp.series(js[version].dev.watch)(() => true);
}
// Pre and postbuild
const baseResources = imgres.allOptimized;
async function deleteEmpty() {
await delEmpty(buildFolder);
}
const postbuild = gulp.series(imgres.cleanupUnusedCssInlineImages, deleteEmpty);
export const step = {
baseResources,
deleteEmpty,
postbuild,
};
///////////////////// RUNNABLE TASKS /////////////////////
// Builds everything (dev)
const prepare = {
dev: gulp.series(
utils.cleanup,
utils.copyAdditionalBuildFiles,
localConfig.findOrCreate,
gulp.parallel(
gulp.series(imgres.buildAtlas, imgres.atlasToJson, imgres.atlas),
sounds.dev,
gulp.series(imgres.copyImageResources, css.dev),
imgres.copyNonImageResources,
translations.fullBuild
)
),
};
/**
* @typedef {import("gulp").TaskFunction} TaskFunction
*/
export const build =
/**
* @type {Record<string, {
* code: TaskFunction,
* resourcesAndCode: TaskFunction,
* all: TaskFunction,
* full: TaskFunction,
* }> & { prepare: typeof prepare }}
*/
({
prepare,
});
/**
* @type {Record<string, Record<string, TaskFunction>>}
*/
const pack = {};
export { pack as package };
/** @type {Record<string, TaskFunction>} */
export const serve = {};
// Builds everything for every variant
for (const variant in BUILD_VARIANTS) {
const data = BUILD_VARIANTS[variant];
// build
const code = gulp.series(
data.standalone ? sounds.fullbuildHQ : sounds.fullbuild,
translations.fullBuild,
js[variant].prod.build
);
const resourcesAndCode = gulp.parallel(step.baseResources, code);
const all = gulp.series(resourcesAndCode, css.prod, html.prod);
const full = gulp.series(utils.cleanup, all, step.postbuild);
build[variant] = { code, resourcesAndCode, all, full };
// Tasks for creating packages. These packages are already distributable, but usually can be further
// wrapped in a different format (an installer for Windows, tarball for Linux, DMG for macOS).
if (data.standalone) {
const packageTasks = [
"win32-x64",
"win32-arm64",
"linux-x64",
"linux-arm64",
"darwin-x64",
"darwin-arm64",
"all",
];
pack[variant] = {};
for (const task of packageTasks) {
pack[variant][task] = gulp.series(
localConfig.findOrCreate,
full,
utils.cleanBuildOutputFolder,
standalone[variant].prepare.all,
standalone[variant].package[task]
);
}
}
// serve
serve[variant] = gulp.series(build.prepare.dev, html.dev, () => serveHTML({ version: variant }));
}
// Deploying!
export const deploy = {
staging: gulp.series(
utils.requireCleanWorkingTree,
build["web-shapezio-beta"].full,
ftp.upload.staging.all
),
prod: gulp.series(utils.requireCleanWorkingTree, build["web-shapezio"].full, ftp.upload.prod.all),
};
export const main = {
prepareDocs: docs.prepareDocs,
webserver,
};
// Default task (dev, localhost)
export default gulp.series(serve["standalone-steam"]);

View File

@ -1,21 +1,20 @@
import path from "path/posix"; import path from "path/posix";
import fs from "fs"; import fs from "fs/promises";
import gulpYaml from "gulp-yaml";
import YAML from "yaml"; import YAML from "yaml";
import gulp from "gulp";
import gulpPlumber from "gulp-plumber"; import gulpPlumber from "gulp-plumber";
import gulpYaml from "gulp-yaml";
const translationsSourceDir = path.join("..", "translations"); const translationsSourceDir = path.join("..", "translations");
const translationsJsonDir = path.join("..", "src", "js", "built-temp"); const translationsJsonDir = path.join("..", "src", "js", "built-temp");
export default function gulptasksTranslations(gulp) { export function convertToJson() {
gulp.task("translations.convertToJson", () => {
return gulp return gulp
.src(path.join(translationsSourceDir, "*.yaml")) .src(path.join(translationsSourceDir, "*.yaml"))
.pipe(gulpPlumber()) .pipe(gulpPlumber())
.pipe(gulpYaml({ space: 2, safe: true })) .pipe(gulpYaml({ space: 2, safe: true }))
.pipe(gulp.dest(translationsJsonDir)); .pipe(gulp.dest(translationsJsonDir));
});
gulp.task("translations.fullBuild", gulp.series("translations.convertToJson"));
} }
export const fullBuild = convertToJson;