ds4/gulpfile.js

344 lines
9.9 KiB
JavaScript

// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
const { rollup } = require("rollup");
const argv = require("yargs").argv;
const fs = require("fs-extra");
const gulp = require("gulp");
const path = require("path");
const rollupConfig = require("./rollup.config");
const semver = require("semver");
const sass = require("gulp-sass")(require("sass"));
const Datastore = require("@seald-io/nedb");
const { Transform } = require("stream");
/********************/
/* CONFIGURATION */
/********************/
const name = path.basename(path.resolve("."));
const sourceDirectory = "./src";
const distDirectory = "./dist";
const stylesDirectory = path.join(sourceDirectory, "scss");
const packsDirectory = path.join(sourceDirectory, "packs");
const stylesExtension = ".scss";
const sourceFileExtension = ".ts";
const staticFiles = ["assets", "fonts", "lang", "templates", "system.json", "template.json"];
const getDownloadURL = (version) => `https://git.f3l.de/dungeonslayers/ds4/-/releases/${version}/downloads/ds4.zip`;
const getChangelogURL = (version) => `https://git.f3l.de/dungeonslayers/ds4/-/releases/${version}`;
/********************/
/* BUILD */
/********************/
/**
* Build the distributable JavaScript code
*/
async function buildCode() {
const build = await rollup({ input: rollupConfig.input, plugins: rollupConfig.plugins });
return build.write(rollupConfig.output);
}
/**
* Build style sheets
*/
function buildStyles() {
return gulp
.src(path.join(stylesDirectory, `${name}${stylesExtension}`))
.pipe(sass().on("error", sass.logError))
.pipe(gulp.dest(path.join(distDirectory, "css")));
}
/**
* Remove unwanted data from a pack entry
*/
function cleanPackEntry(entry, cleanSourceId = true) {
if (cleanSourceId) {
delete entry.flags?.core?.sourceId;
}
Object.keys(entry.flags).forEach((scope) => {
if (Object.keys(entry.flags[scope]).length === 0) {
delete entry.flags[scope];
}
});
if (entry.permission) entry.permission = { default: 0 };
const embeddedDocumentCollections = [
"drawings",
"effects",
"items",
"lights",
"notes",
"results",
"sounds",
"templates",
"tiles",
"tokens",
"walls",
];
embeddedDocumentCollections
.flatMap((embeddedDocumentCollection) => entry[embeddedDocumentCollection] ?? [])
.forEach((embeddedEntry) => cleanPackEntry(embeddedEntry, false));
return entry;
}
/**
* Convert a stream of JSON files to NeDB files
*/
const jsonToNeDB = () =>
new Transform({
transform(file, _, callback) {
try {
file.contents = Buffer.from(
JSON.parse(file.contents.toString())
.map((entry) => cleanPackEntry(entry))
.map((entry) => JSON.stringify(entry))
.join("\n") + "\n",
);
file.path = path.join(
path.dirname(file.path),
path.basename(file.path, path.extname(file.path)) + ".db",
);
callback(undefined, file);
} catch (err) {
callback(err);
}
},
objectMode: true,
});
/**
* build compendium packs
*/
function buildPacks() {
return gulp
.src(path.join(packsDirectory, "*.json"))
.pipe(jsonToNeDB())
.pipe(gulp.dest(path.join(distDirectory, "packs")));
}
/**
* Convert a stream of NeDB files to JSON files
*/
const neDBToJSON = () =>
new Transform({
transform(file, _, callback) {
try {
const db = new Datastore({ filename: file.path, autoload: true });
db.find({}, (err, docs) => {
if (err) {
callback(err);
} else {
file.contents = Buffer.from(
JSON.stringify(
docs.map((entry) => cleanPackEntry(entry)),
undefined,
4,
) + "\n",
);
file.path = path.join(
path.dirname(file.path),
path.basename(file.path, path.extname(file.path)) + ".json",
);
callback(undefined, file);
}
});
} catch (err) {
callback(err);
}
},
objectMode: true,
});
/**
* Generate JSON files from the compendium packs in the distribution directory
*/
function generateJSONsFromPacks() {
return gulp
.src(path.join(distDirectory, "packs", "*.db"))
.pipe(neDBToJSON())
.pipe(gulp.dest(path.join(packsDirectory)));
}
/**
* Copy static files
*/
async function copyFiles() {
for (const file of staticFiles) {
if (fs.existsSync(path.join(sourceDirectory, file))) {
await fs.copy(path.join(sourceDirectory, file), path.join(distDirectory, file));
}
}
}
/**
* Watch for changes for each build step
*/
function buildWatch() {
gulp.watch(
path.join(sourceDirectory, "**", `*${sourceFileExtension}`).replace(/\\/g, "/"),
{ ignoreInitial: false },
buildCode,
);
gulp.watch(
path.join(stylesDirectory, "**", `*${stylesExtension}`).replace(/\\/g, "/"),
{ ignoreInitial: false },
buildStyles,
);
gulp.watch(path.join(packsDirectory, "**", "*.json").replace(/\\/g, "/"), { ignoreInitial: false }, buildPacks);
gulp.watch(
staticFiles.map((file) => path.join(sourceDirectory, file).replace(/\\/g, "/")),
{ ignoreInitial: false },
copyFiles,
);
}
/********************/
/* CLEAN */
/********************/
/**
* Remove built files from `dist` folder while ignoring source files
*/
async function clean() {
const files = [...staticFiles, "packs", "module"];
if (fs.existsSync(path.join(stylesDirectory, `${name}${stylesExtension}`))) {
files.push("css");
}
console.log(" ", "Files to clean:");
console.log(" ", files.join("\n "));
for (const filePath of files) {
await fs.remove(path.join(distDirectory, filePath));
}
}
/********************/
/* LINK */
/********************/
/**
* Get the data path of Foundry VTT based on what is configured in `foundryconfig.json`
*/
function getDataPath() {
const config = fs.readJSONSync("foundryconfig.json");
if (config?.dataPath) {
if (!fs.existsSync(path.resolve(config.dataPath))) {
throw new Error("User Data path invalid, no Data directory found");
}
return path.resolve(config.dataPath);
} else {
throw new Error("No User Data path defined in foundryconfig.json");
}
}
/**
* Link build to User Data folder
*/
async function linkUserData() {
let destinationDirectory;
if (fs.existsSync(path.resolve(".", sourceDirectory, "system.json"))) {
destinationDirectory = "systems";
} else {
throw new Error("Could not find system.json");
}
const linkDirectory = path.resolve(getDataPath(), destinationDirectory, name);
if (argv.clean || argv.c) {
console.log(`Removing build in ${linkDirectory}.`);
await fs.remove(linkDirectory);
} else if (!fs.existsSync(linkDirectory)) {
console.log(`Copying build to ${linkDirectory}.`);
await fs.ensureDir(path.resolve(linkDirectory, ".."));
await fs.symlink(path.resolve(".", distDirectory), linkDirectory);
}
}
/********************/
/* VERSIONING */
/********************/
/**
* Get the contents of the manifest file as object.
*/
function getManifest() {
const manifestPath = path.join(sourceDirectory, "system.json");
if (fs.existsSync(manifestPath)) {
return {
file: fs.readJSONSync(manifestPath),
name: "system.json",
};
}
}
/**
* Get the target version based on on the current version and the argument passed as release.
*/
function getTargetVersion(currentVersion, release) {
if (["major", "premajor", "minor", "preminor", "patch", "prepatch", "prerelease"].includes(release)) {
return semver.inc(currentVersion, release);
} else {
return semver.valid(release);
}
}
/**
* Update version and download URL.
*/
function bumpVersion(cb) {
const packageJson = fs.readJSONSync("package.json");
const manifest = getManifest();
if (!manifest) cb(Error("Manifest JSON not found"));
try {
const release = argv.release || argv.r;
const currentVersion = packageJson.version;
if (!release) {
return cb(Error("Missing release type"));
}
const targetVersion = getTargetVersion(currentVersion, release);
if (!targetVersion) {
return cb(new Error("Error: Incorrect version arguments"));
}
if (targetVersion === currentVersion) {
return cb(new Error("Error: Target version is identical to current version"));
}
console.log(`Updating version number to '${targetVersion}'`);
packageJson.version = targetVersion;
fs.writeJSONSync("package.json", packageJson, { spaces: 4 });
manifest.file.version = targetVersion;
manifest.file.download = getDownloadURL(targetVersion);
manifest.file.changelog = getChangelogURL(targetVersion);
fs.writeJSONSync(path.join(sourceDirectory, manifest.name), manifest.file, { spaces: 4 });
return cb();
} catch (err) {
cb(err);
}
}
const execBuild = gulp.parallel(buildCode, buildStyles, buildPacks, copyFiles);
exports.build = gulp.series(clean, execBuild);
exports.watch = buildWatch;
exports.clean = clean;
exports.link = linkUserData;
exports.bumpVersion = bumpVersion;
exports.generateJSONsFromPacks = generateJSONsFromPacks;