chore: replace gulp by a pure rollup based build

This commit is contained in:
Johannes Loher 2021-11-30 17:30:25 +01:00
parent 294fbefd0a
commit 0d6b89a3ed
16 changed files with 1772 additions and 2573 deletions

View File

@ -24,7 +24,7 @@ module.exports = {
overrides: [
{
files: ["./*.js"],
files: ["./*.cjs", "./*.js"],
rules: {
"@typescript-eslint/no-var-requires": "off",
},

View File

@ -27,9 +27,7 @@ https://git.f3l.de/api/v4/projects/dungeonslayers%2Fds4/packages/generic/ds4/lat
In order to build this system, recent versions of `node` and `yarn` are
required. Most likely using `npm` also works but only `yarn` is officially
supported. We recommend using the latest lts version of `node`, which is
`v14.17.1` at the time of writing. If you use `nvm` to manage your `node`
versions, you can simply run
supported. We recommend using the latest lts version of `node`. If you use `nvm` to manage your `node` versions, you can simply run
```
nvm install
@ -37,7 +35,7 @@ nvm install
in the project's root directory.
You also need to install the the project's dependencies. To do so, run
You also need to install the project's dependencies. To do so, run
```
yarn install
@ -54,7 +52,7 @@ yarn build
Alternatively, you can run
```
yarn build:watch
yarn watch
```
to watch for changes and automatically build as necessary.
@ -77,7 +75,7 @@ On platforms other than Linux you need to adjust the path accordingly.
Then run
```
yarn link-project
yarn link-package
```
### Running the tests

View File

@ -1,7 +0,0 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
const config = require("conventional-changelog-conventionalcommits");
module.exports = config();

View File

@ -1,344 +0,0 @@
// 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;

View File

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT
module.exports = {
export default {
preset: "ts-jest",
globals: {
"ts-jest": {

View File

@ -36,26 +36,31 @@
"name": "Sascha Martens"
}
],
"type": "module",
"scripts": {
"build": "gulp build",
"build:watch": "gulp watch",
"link-project": "gulp link",
"clean": "gulp clean",
"clean:link": "gulp link --clean",
"bump-version": "gulp bumpVersion",
"lint": "eslint --ext .ts,.js .",
"lint:fix": "eslint --ext .ts,.js --fix .",
"build": "run-s clean:files build:files",
"build:files": "rollup -c",
"watch": "rollup -c -w",
"link-package": "node ./tools/link-package.js",
"clean": "run-p clean:files clean:link",
"clean:files": "rimraf dist",
"clean:link": "node ./tools/link-package.js --clean",
"lint": "eslint --ext .ts,.js,.cjs,.mjs .",
"lint:fix": "eslint --ext .ts,.js,.cjs,.mjs --fix .",
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --ci --reporters=default --reporters=jest-junit",
"format": "prettier --write \"./**/*.(ts|js|json|scss|yml)\"",
"format": "prettier --write \"./**/*.(ts|js|cjs|mjs|json|scss|yml)\"",
"typecheck": "tsc --noEmit",
"bump-version": "node ./tools/bump-version.js",
"convert-packs-to-json": "node ./tools/convert-packs-to-json.js",
"postinstall": "husky install",
"changelog": "conventional-changelog -n changelog.config.js -o CHANGELOG.md -r 2"
"changelog": "conventional-changelog -p conventionalcommits -o CHANGELOG.md -r 2"
},
"devDependencies": {
"@commitlint/cli": "15.0.0",
"@commitlint/config-conventional": "15.0.0",
"@guanghechen/rollup-plugin-copy": "1.8.4",
"@league-of-foundry-developers/foundry-vtt-types": "0.8.9-7",
"@seald-io/nedb": "2.2.0",
"@types/fs-extra": "9.0.13",
@ -69,15 +74,16 @@
"eslint-plugin-jest": "25.3.0",
"eslint-plugin-prettier": "4.0.0",
"fs-extra": "10.0.0",
"gulp": "4.0.2",
"gulp-sass": "5.0.0",
"husky": "7.0.4",
"jest": "27.4.2",
"jest-junit": "13.0.0",
"lint-staged": "12.1.2",
"npm-run-all": "4.1.5",
"prettier": "2.5.0",
"rimraf": "3.0.2",
"rollup": "2.60.2",
"rollup-plugin-sourcemaps": "0.6.3",
"rollup-plugin-styles": "3.14.1",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-typescript2": "0.31.1",
"sass": "1.44.0",
@ -88,7 +94,7 @@
"yargs": "17.2.1"
},
"lint-staged": {
"*.ts": "eslint --cache --fix",
"*.(ts|js|cjs|mjs)": "eslint --cache --fix",
"*.(json|scss|yml)": "prettier --write"
}
}

View File

@ -2,25 +2,50 @@
//
// SPDX-License-Identifier: MIT
const typescript = require("rollup-plugin-typescript2");
const sourcemaps = require("rollup-plugin-sourcemaps");
const { terser } = require("rollup-plugin-terser");
import copy from "@guanghechen/rollup-plugin-copy";
import typescript from "rollup-plugin-typescript2";
import sourcemaps from "rollup-plugin-sourcemaps";
import styles from "rollup-plugin-styles";
import { terser } from "rollup-plugin-terser";
import { distDirectory, name, sourceDirectory } from "./tools/const.js";
import { convertJSONToPack } from "./tools/json-pack-tools.js";
const staticFiles = ["template.json", "system.json", "assets", "fonts", "lang", "templates"];
const isProduction = process.env.NODE_ENV === "production";
/**
* @type {import('rollup').RollupOptions}
*/
const config = {
input: "src/module/ds4.ts",
input: { [`module/${name}`]: `${sourceDirectory}/module/${name}.ts` },
output: {
dir: "dist/module",
dir: distDirectory,
format: "es",
sourcemap: true,
assetFileNames: "[name].[ext]",
},
plugins: [
sourcemaps(),
typescript({}),
process.env.NODE_ENV === "production" && terser({ ecma: 2020, keep_fnames: true }),
typescript(),
styles({
mode: ["extract", `css/${name}.css`],
url: false,
sourceMap: true,
minimize: isProduction,
}),
copy({
targets: [
{ src: staticFiles.map((file) => `${sourceDirectory}/${file}`), dest: distDirectory },
{
src: [`${sourceDirectory}/packs/*.json`],
dest: `${distDirectory}/packs`,
rename: (name) => `${name}.db`,
transform: convertJSONToPack,
},
],
}),
isProduction && terser({ ecma: 2020, keep_fnames: true }),
],
};
module.exports = config;
export default config;

View File

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: MIT
import "../scss/ds4.scss";
import registerForHooks from "./hooks/hooks";
registerForHooks();

84
tools/bump-version.js Normal file
View File

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
import fs from "fs-extra";
import path from "node:path";
import semver from "semver";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { sourceDirectory } from "./const.js";
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}`;
/**
* Get the contents of the manifest file as object.
* @returns {{file: unknown, name: string}} An object describing the manifest
*/
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.
* @param {string} currentVersion The current version
* @param {semver.ReleaseType | string} release Either a semver release type or a valid semver version
* @returns {string | null} The target version
*/
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.
* @param {semver.ReleaseType | string} release Either a semver release type or a valid semver version
*/
function bumpVersion(release) {
if (!release) {
throw new Error("Missing release type");
}
const packageJson = fs.readJSONSync("package.json");
const manifest = getManifest();
if (!manifest) throw new Error("Manifest JSON not found");
const currentVersion = packageJson.version;
const targetVersion = getTargetVersion(currentVersion, release);
if (!targetVersion) {
throw new Error("Incorrect version arguments");
}
if (targetVersion === currentVersion) {
throw new Error("Target version is identical to current version");
}
console.log(`Bumping 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 });
}
const argv = yargs(hideBin(process.argv)).usage("Usage: $0").option("release", {
alias: "r",
type: "string",
demandOption: true,
description: "Either a semver release type or a valid semver version",
}).argv;
const release = argv.r;
bumpVersion(release);

9
tools/const.js Normal file
View File

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
export const name = "ds4";
export const sourceDirectory = "./src";
export const distDirectory = "./dist";
export const destinationDirectory = "systems";
export const foundryconfigFile = "./foundryconfig.json";

View File

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
import promises from "node:fs/promises";
import path from "node:path";
import { distDirectory, sourceDirectory } from "./const.js";
import { convertPackFileToJSONFile } from "./json-pack-tools.js";
const packsDistDirectory = path.join(distDirectory, "packs");
const packsSourceDirectory = path.join(sourceDirectory, "packs");
console.log(`Converting pack files in ${packsDistDirectory} to json files in ${packsSourceDirectory}:`);
const conversionPromises = (await promises.readdir(packsDistDirectory, { withFileTypes: true }))
.filter((dirent) => dirent.isFile() && path.extname(dirent.name))
.map(async (dirent) => convertPackFileToJSONFile(path.join(packsDistDirectory, dirent.name), packsSourceDirectory));
await Promise.all(conversionPromises);

95
tools/json-pack-tools.js Normal file
View File

@ -0,0 +1,95 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
import promises from "node:fs/promises";
import path from "node:path";
import Datastore from "@seald-io/nedb";
/**
* Removes 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;
}
/**
* Converts a JSON string containing an array to a Pack (NeDB) string.
* @param {string} jsonString The input JSON string
* @returns {string} The resulting Pack string
*/
export function convertJSONToPack(jsonString) {
return (
JSON.parse(jsonString)
.map((entry) => cleanPackEntry(entry))
.map((entry) => JSON.stringify(entry))
.join("\n") + "\n"
);
}
/**
* Converts a pack file (NeDB) to a JSON string.
* @param {string} filename The name of the pack file
* @returns {Promise<Array<unknown>>} A promise that resolves to an array of the documents in the pack file
*/
function convertPackFileToJSON(filename) {
const db = new Datastore({ filename, autoload: true });
return new Promise((resolve, reject) => {
db.find({}, (err, docs) => {
if (err) {
reject(err);
} else {
resolve(
JSON.stringify(
docs.map((entry) => cleanPackEntry(entry)),
undefined,
4,
) + "\n",
);
}
});
});
}
/**
* Converts a pack file (NeDB) to a JSON file and puts it in the given directory. If no directory is given, it is put
* into the same directory as the pack file.
* @param {string} filename The name of the pack file
* @param {string} [directory] A directory to put the json file into
* @returns {Promise<void>} A promise that resolves once the JSON file has been written
*/
export async function convertPackFileToJSONFile(filename, directory) {
if (directory === undefined) {
directory = path.dirname(filename);
}
console.log(" ", path.basename(filename));
const jsonFilePath = path.join(directory, `${path.basename(filename, path.extname(filename))}.json`);
const json = await convertPackFileToJSON(filename);
await promises.writeFile(jsonFilePath, json);
}

55
tools/link-package.js Normal file
View File

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT
import fs from "fs-extra";
import path from "node:path";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { destinationDirectory, distDirectory, foundryconfigFile, name, sourceDirectory } from "./const.js";
/**
* Get the data path of Foundry VTT based on what is configured in the {@link foundryconfigFile}.
*/
function getDataPath() {
const config = fs.readJSONSync(foundryconfigFile);
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 ${foundryconfigFile}`);
}
}
/**
* Link the built package to the user data folder.
* @param {boolean} clean Whether to remove the link instead of creating it
*/
async function linkPackage(clean) {
if (!fs.existsSync(path.resolve(sourceDirectory, "system.json"))) {
throw new Error("Could not find system.json");
}
const linkDirectory = path.resolve(getDataPath(), destinationDirectory, name);
if (clean) {
console.log(`Removing link to built package at ${linkDirectory}.`);
await fs.remove(linkDirectory);
} else if (!fs.existsSync(linkDirectory)) {
console.log(`Linking built package to ${linkDirectory}.`);
await fs.ensureDir(path.resolve(linkDirectory, ".."));
await fs.symlink(path.resolve(".", distDirectory), linkDirectory);
}
}
const argv = yargs(hideBin(process.argv)).usage("Usage: $0").option("clean", {
alias: "c",
type: "boolean",
default: false,
description: "Remove the link instead of creating it",
}).argv;
const clean = argv.c;
await linkPackage(clean);

3643
yarn.lock

File diff suppressed because it is too large Load Diff