Merge branch 'master' into 014_track_talents
This commit is contained in:
commit
5bd43125f9
6 changed files with 527 additions and 454 deletions
|
@ -45,11 +45,12 @@ build:
|
|||
stage: build
|
||||
script:
|
||||
- npm run build
|
||||
- mv dist ds4
|
||||
cache:
|
||||
<<: *global_cache
|
||||
artifacts:
|
||||
paths:
|
||||
- dist
|
||||
- ds4
|
||||
expire_in: 1 week
|
||||
|
||||
deploy:
|
||||
|
@ -58,7 +59,7 @@ deploy:
|
|||
dependencies:
|
||||
- build
|
||||
script:
|
||||
- rsync --delete -az ./dist/ rsync://${DEPLOYMENT_USER}@${DEPLOYMENT_SERVER}:${DEPLOYMENT_PATH}
|
||||
- rsync --delete -az ./ds4/ rsync://${DEPLOYMENT_USER}@${DEPLOYMENT_SERVER}:${DEPLOYMENT_PATH}
|
||||
environment:
|
||||
name: production
|
||||
url: https://vtt.f3l.de/
|
||||
|
|
29
.gitlab/issue_templates/Bug Report.md
Normal file
29
.gitlab/issue_templates/Bug Report.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Description
|
||||
|
||||
Please describe the issue.
|
||||
|
||||
# Steps to Reproduce
|
||||
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
# Expected Behavior
|
||||
|
||||
Please describe the expected behavior.
|
||||
|
||||
# Actual Behavior
|
||||
|
||||
Please describe the actual behavior.
|
||||
|
||||
# Additional Details
|
||||
|
||||
These are optional, please add them if it makes sense.
|
||||
|
||||
- ![Screenshot]()
|
||||
- [Logfile]()
|
||||
- ...
|
||||
|
||||
# Possible Solutions
|
||||
|
||||
If you have any suggestions on how to solve the issue, please add them here.
|
|
@ -1,9 +1,13 @@
|
|||
# Description
|
||||
# Story
|
||||
|
||||
As a …, I want … so that …
|
||||
|
||||
# Description
|
||||
|
||||
Please add a more detailed description of the feature here.
|
||||
|
||||
# Acceptance criteria
|
||||
|
||||
* Criterion 1
|
||||
* Criterion 2
|
||||
* …
|
||||
1. Criterion 1
|
||||
2. Criterion 2
|
||||
3. …
|
||||
|
|
73
README.md
73
README.md
|
@ -3,50 +3,89 @@
|
|||
An implementation of the Dungeonslayers 4 game system for [Foundry Virtual
|
||||
Tabletop](http://foundryvtt.com).
|
||||
|
||||
## Prerequisites
|
||||
This system provides character sheet support for Actors and Items and mechanical
|
||||
support for dice and rules necessary to
|
||||
play games of Dungeponslayers 4.
|
||||
|
||||
In order to build this system, a recent version of `npm` is required.
|
||||
## Installation
|
||||
|
||||
## Building
|
||||
To install and use the Dungeonslayers 4 system for Foundry Virtual Tabletop,
|
||||
simply paste the following URL into the **Install System** dialog on the Setup
|
||||
menu of the application.
|
||||
|
||||
To build the system, first install all required dependencies:
|
||||
https://git.f3l.de/dungeonslayers/ds4/-/raw/master/src/system.json?inline=false
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisits
|
||||
|
||||
In order to build this system, recent versions of `node` and `npm` are required.
|
||||
We recommend using the latest lts version of `node`, which is `v14.15.4` at the
|
||||
time of writing. If you use `nvm` to manage your `node` versions, you can simply
|
||||
run
|
||||
```
|
||||
nvm install
|
||||
```
|
||||
|
||||
in the project's root directory.
|
||||
|
||||
You also need to install the the project's dependencies. To do so, run
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
Then build the project by running
|
||||
### Building
|
||||
|
||||
You can build the project by running
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
If you'd like the built system to be automatically linked to your local Foundry
|
||||
VTT installation's data folder, add a file called `foundryconfig.json` to the
|
||||
project root with the following contents:
|
||||
Alternatively, you can run
|
||||
|
||||
```
|
||||
npm run build:watch
|
||||
```
|
||||
|
||||
to watch for changes and automatically build as necessary.
|
||||
|
||||
### Linking the built system to Foundry VTT
|
||||
|
||||
In order to provide a fluent development experience, it is recommended to link
|
||||
the built system to your local Foundry VTT installation's data folder. In order
|
||||
to do so, first add a file called `foundryconfig.json` to the project root with
|
||||
the following content:
|
||||
|
||||
```
|
||||
{
|
||||
"dataPath": "/<absolute path to your home>/.local/share/FoundryVTT",
|
||||
"repository": "",
|
||||
"rawURL": ""
|
||||
"dataPath": "<path to your home directory>/.local/share/FoundryVTT"
|
||||
}
|
||||
```
|
||||
|
||||
On platforms other than Linux you need to adjust the path accordingly.
|
||||
|
||||
Then run
|
||||
|
||||
```
|
||||
npm run link
|
||||
```
|
||||
|
||||
If you want the system to be continuously build upon every saved change, just
|
||||
run
|
||||
### Running the tests
|
||||
|
||||
You can run the tests with the following command:
|
||||
|
||||
```
|
||||
npm run build:watch
|
||||
npm test
|
||||
```
|
||||
|
||||
# Licensing
|
||||
## Contributing
|
||||
|
||||
Code and content contributions are accepted. Please feel free to submit issues
|
||||
to the issue tracker or submit merge requests for code changes.
|
||||
|
||||
## Licensing
|
||||
|
||||
Dungeonslayers (© Christian Kennig) is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/de/deed.en).
|
||||
|
||||
|
@ -56,5 +95,5 @@ CC BY-NC-SA 3.0. Hence the modified icons are also published under this
|
|||
license. A copy of this license can be found under
|
||||
[src/assets/official/LICENSE](src/assets/official/LICENSE).
|
||||
|
||||
The rest of this project is licensed under the MIT License, a copy of which can
|
||||
be found under [LICENSE](LICENSE).
|
||||
The software component of this project is licensed under the MIT License, a copy
|
||||
of which can be found under [LICENSE](LICENSE).
|
||||
|
|
858
gulpfile.js
858
gulpfile.js
|
@ -1,429 +1,429 @@
|
|||
const gulp = require("gulp");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const chalk = require("chalk");
|
||||
const archiver = require("archiver");
|
||||
const stringify = require("json-stringify-pretty-compact");
|
||||
const typescript = require("typescript");
|
||||
|
||||
const ts = require("gulp-typescript");
|
||||
const less = require("gulp-less");
|
||||
const sass = require("gulp-sass");
|
||||
const git = require("gulp-git");
|
||||
|
||||
const argv = require("yargs").argv;
|
||||
|
||||
sass.compiler = require("sass");
|
||||
|
||||
function getConfig() {
|
||||
const configPath = path.resolve(process.cwd(), "foundryconfig.json");
|
||||
let config;
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
config = fs.readJSONSync(configPath);
|
||||
return config;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function getManifest() {
|
||||
const json = {};
|
||||
|
||||
if (fs.existsSync("src")) {
|
||||
json.root = "src";
|
||||
} else {
|
||||
json.root = "dist";
|
||||
}
|
||||
|
||||
const modulePath = path.join(json.root, "module.json");
|
||||
const systemPath = path.join(json.root, "system.json");
|
||||
|
||||
if (fs.existsSync(modulePath)) {
|
||||
json.file = fs.readJSONSync(modulePath);
|
||||
json.name = "module.json";
|
||||
} else if (fs.existsSync(systemPath)) {
|
||||
json.file = fs.readJSONSync(systemPath);
|
||||
json.name = "system.json";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeScript transformers
|
||||
* @returns {typescript.TransformerFactory<typescript.SourceFile>}
|
||||
*/
|
||||
function createTransformer() {
|
||||
/**
|
||||
* @param {typescript.Node} node
|
||||
*/
|
||||
function shouldMutateModuleSpecifier(node) {
|
||||
if (!typescript.isImportDeclaration(node) && !typescript.isExportDeclaration(node)) return false;
|
||||
if (node.moduleSpecifier === undefined) return false;
|
||||
if (!typescript.isStringLiteral(node.moduleSpecifier)) return false;
|
||||
if (!node.moduleSpecifier.text.startsWith("./") && !node.moduleSpecifier.text.startsWith("../")) return false;
|
||||
if (path.extname(node.moduleSpecifier.text) !== "") return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms import/export declarations to append `.js` extension
|
||||
* @param {typescript.TransformationContext} context
|
||||
*/
|
||||
function importTransformer(context) {
|
||||
return (node) => {
|
||||
/**
|
||||
* @param {typescript.Node} node
|
||||
*/
|
||||
function visitor(node) {
|
||||
if (shouldMutateModuleSpecifier(node)) {
|
||||
if (typescript.isImportDeclaration(node)) {
|
||||
const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`);
|
||||
return typescript.updateImportDeclaration(
|
||||
node,
|
||||
node.decorators,
|
||||
node.modifiers,
|
||||
node.importClause,
|
||||
newModuleSpecifier
|
||||
);
|
||||
} else if (typescript.isExportDeclaration(node)) {
|
||||
const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`);
|
||||
return typescript.updateExportDeclaration(
|
||||
node,
|
||||
node.decorators,
|
||||
node.modifiers,
|
||||
node.exportClause,
|
||||
newModuleSpecifier
|
||||
);
|
||||
}
|
||||
}
|
||||
return typescript.visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
return typescript.visitNode(node, visitor);
|
||||
};
|
||||
}
|
||||
|
||||
return importTransformer;
|
||||
}
|
||||
|
||||
const tsConfig = ts.createProject("tsconfig.json", {
|
||||
getCustomTransformers: (_program) => ({
|
||||
after: [createTransformer()],
|
||||
}),
|
||||
});
|
||||
|
||||
/********************/
|
||||
/* BUILD */
|
||||
/********************/
|
||||
|
||||
/**
|
||||
* Build TypeScript
|
||||
*/
|
||||
function buildTS() {
|
||||
return gulp.src("src/**/*.ts").pipe(tsConfig()).pipe(gulp.dest("dist"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Less
|
||||
*/
|
||||
function buildLess() {
|
||||
return gulp.src("src/*.less").pipe(less()).pipe(gulp.dest("dist"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SASS
|
||||
*/
|
||||
function buildSASS() {
|
||||
return gulp.src("src/*.scss").pipe(sass().on("error", sass.logError)).pipe(gulp.dest("dist"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy static files
|
||||
*/
|
||||
async function copyFiles() {
|
||||
const statics = ["lang", "fonts", "assets", "templates", "module.json", "system.json", "template.json"];
|
||||
try {
|
||||
for (const file of statics) {
|
||||
if (fs.existsSync(path.join("src", file))) {
|
||||
await fs.copy(path.join("src", file), path.join("dist", file));
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for changes for each build step
|
||||
*/
|
||||
function buildWatch() {
|
||||
gulp.watch("src/**/*.ts", { ignoreInitial: false }, buildTS);
|
||||
gulp.watch("src/**/*.less", { ignoreInitial: false }, buildLess);
|
||||
gulp.watch("src/**/*.scss", { ignoreInitial: false }, buildSASS);
|
||||
gulp.watch(["src/fonts", "src/lang", "src/templates", "src/*.json"], { ignoreInitial: false }, copyFiles);
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* CLEAN */
|
||||
/********************/
|
||||
|
||||
/**
|
||||
* Remove built files from `dist` folder
|
||||
* while ignoring source files
|
||||
*/
|
||||
async function clean() {
|
||||
const name = path.basename(path.resolve("."));
|
||||
const files = [];
|
||||
|
||||
// If the project uses TypeScript
|
||||
if (fs.existsSync(path.join("src", `${name}.ts`))) {
|
||||
files.push(
|
||||
"lang",
|
||||
"templates",
|
||||
"assets",
|
||||
"module",
|
||||
`${name}.js`,
|
||||
"module.json",
|
||||
"system.json",
|
||||
"template.json"
|
||||
);
|
||||
}
|
||||
|
||||
// If the project uses Less or SASS
|
||||
if (fs.existsSync(path.join("src", `${name}.less`)) || fs.existsSync(path.join("src", `${name}.scss`))) {
|
||||
files.push("fonts", `${name}.css`);
|
||||
}
|
||||
|
||||
console.log(" ", chalk.yellow("Files to clean:"));
|
||||
console.log(" ", chalk.blueBright(files.join("\n ")));
|
||||
|
||||
// Attempt to remove the files
|
||||
try {
|
||||
for (const filePath of files) {
|
||||
await fs.remove(path.join("dist", filePath));
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* LINK */
|
||||
/********************/
|
||||
|
||||
/**
|
||||
* Link build to User Data folder
|
||||
*/
|
||||
async function linkUserData() {
|
||||
const name = path.basename(path.resolve("."));
|
||||
const config = fs.readJSONSync("foundryconfig.json");
|
||||
|
||||
let destDir;
|
||||
try {
|
||||
if (
|
||||
fs.existsSync(path.resolve(".", "dist", "module.json")) ||
|
||||
fs.existsSync(path.resolve(".", "src", "module.json"))
|
||||
) {
|
||||
destDir = "modules";
|
||||
} else if (
|
||||
fs.existsSync(path.resolve(".", "dist", "system.json")) ||
|
||||
fs.existsSync(path.resolve(".", "src", "system.json"))
|
||||
) {
|
||||
destDir = "systems";
|
||||
} else {
|
||||
throw Error(`Could not find ${chalk.blueBright("module.json")} or ${chalk.blueBright("system.json")}`);
|
||||
}
|
||||
|
||||
let linkDir;
|
||||
if (config.dataPath) {
|
||||
if (!fs.existsSync(path.join(config.dataPath, "Data")))
|
||||
throw Error("User Data path invalid, no Data directory found");
|
||||
|
||||
linkDir = path.join(config.dataPath, "Data", destDir, name);
|
||||
} else {
|
||||
throw Error("No User Data path defined in foundryconfig.json");
|
||||
}
|
||||
|
||||
if (argv.clean || argv.c) {
|
||||
console.log(chalk.yellow(`Removing build in ${chalk.blueBright(linkDir)}`));
|
||||
|
||||
await fs.remove(linkDir);
|
||||
} else if (!fs.existsSync(linkDir)) {
|
||||
console.log(chalk.green(`Copying build to ${chalk.blueBright(linkDir)}`));
|
||||
await fs.symlink(path.resolve("./dist"), linkDir);
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* PACKAGE */
|
||||
/*********************/
|
||||
|
||||
/**
|
||||
* Package build
|
||||
*/
|
||||
async function packageBuild() {
|
||||
const manifest = getManifest();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// Remove the package dir without doing anything else
|
||||
if (argv.clean || argv.c) {
|
||||
console.log(chalk.yellow("Removing all packaged files"));
|
||||
fs.removeSync("package");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure there is a directory to hold all the packaged versions
|
||||
fs.ensureDirSync("package");
|
||||
|
||||
// Initialize the zip file
|
||||
const zipName = `${manifest.file.name}-v${manifest.file.version}.zip`;
|
||||
const zipFile = fs.createWriteStream(path.join("package", zipName));
|
||||
const zip = archiver("zip", { zlib: { level: 9 } });
|
||||
|
||||
zipFile.on("close", () => {
|
||||
console.log(chalk.green(zip.pointer() + " total bytes"));
|
||||
console.log(chalk.green(`Zip file ${zipName} has been written`));
|
||||
return resolve();
|
||||
});
|
||||
|
||||
zip.on("error", (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
zip.pipe(zipFile);
|
||||
|
||||
// Add the directory with the final code
|
||||
zip.directory("dist/", manifest.file.name);
|
||||
|
||||
zip.finalize();
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* PACKAGE */
|
||||
/*********************/
|
||||
|
||||
/**
|
||||
* Update version and URLs in the manifest JSON
|
||||
*/
|
||||
function updateManifest(cb) {
|
||||
const packageJson = fs.readJSONSync("package.json");
|
||||
const config = getConfig(),
|
||||
manifest = getManifest(),
|
||||
rawURL = config.rawURL,
|
||||
repoURL = config.repository,
|
||||
manifestRoot = manifest.root;
|
||||
|
||||
if (!config) cb(Error(chalk.red("foundryconfig.json not found")));
|
||||
if (!manifest) cb(Error(chalk.red("Manifest JSON not found")));
|
||||
if (!rawURL || !repoURL) cb(Error(chalk.red("Repository URLs not configured in foundryconfig.json")));
|
||||
|
||||
try {
|
||||
const version = argv.update || argv.u;
|
||||
|
||||
/* Update version */
|
||||
|
||||
const versionMatch = /^(\d{1,}).(\d{1,}).(\d{1,})$/;
|
||||
const currentVersion = manifest.file.version;
|
||||
let targetVersion = "";
|
||||
|
||||
if (!version) {
|
||||
cb(Error("Missing version number"));
|
||||
}
|
||||
|
||||
if (versionMatch.test(version)) {
|
||||
targetVersion = version;
|
||||
} else {
|
||||
targetVersion = currentVersion.replace(versionMatch, (substring, major, minor, patch) => {
|
||||
console.log(substring, Number(major) + 1, Number(minor) + 1, Number(patch) + 1);
|
||||
if (version === "major") {
|
||||
return `${Number(major) + 1}.0.0`;
|
||||
} else if (version === "minor") {
|
||||
return `${major}.${Number(minor) + 1}.0`;
|
||||
} else if (version === "patch") {
|
||||
return `${major}.${minor}.${Number(patch) + 1}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (targetVersion === "") {
|
||||
return cb(Error(chalk.red("Error: Incorrect version arguments.")));
|
||||
}
|
||||
|
||||
if (targetVersion === currentVersion) {
|
||||
return cb(Error(chalk.red("Error: Target version is identical to current version.")));
|
||||
}
|
||||
console.log(`Updating version number to '${targetVersion}'`);
|
||||
|
||||
packageJson.version = targetVersion;
|
||||
manifest.file.version = targetVersion;
|
||||
|
||||
/* Update URLs */
|
||||
|
||||
const result = `${rawURL}/v${manifest.file.version}/package/${manifest.file.name}-v${manifest.file.version}.zip`;
|
||||
|
||||
manifest.file.url = repoURL;
|
||||
manifest.file.manifest = `${rawURL}/master/${manifestRoot}/${manifest.name}`;
|
||||
manifest.file.download = result;
|
||||
|
||||
const prettyProjectJson = stringify(manifest.file, {
|
||||
maxLength: 35,
|
||||
indent: "\t",
|
||||
});
|
||||
|
||||
fs.writeJSONSync("package.json", packageJson, { spaces: "\t" });
|
||||
fs.writeFileSync(path.join(manifest.root, manifest.name), prettyProjectJson, "utf8");
|
||||
|
||||
return cb();
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
}
|
||||
|
||||
function gitAdd() {
|
||||
return gulp.src("package").pipe(git.add({ args: "--no-all" }));
|
||||
}
|
||||
|
||||
function gitCommit() {
|
||||
return gulp.src("./*").pipe(
|
||||
git.commit(`v${getManifest().file.version}`, {
|
||||
args: "-a",
|
||||
disableAppendPaths: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function gitTag() {
|
||||
const manifest = getManifest();
|
||||
return git.tag(`v${manifest.file.version}`, `Updated to ${manifest.file.version}`, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
|
||||
const execGit = gulp.series(gitAdd, gitCommit, gitTag);
|
||||
|
||||
const execBuild = gulp.parallel(buildTS, buildLess, buildSASS, copyFiles);
|
||||
|
||||
exports.build = gulp.series(clean, execBuild);
|
||||
exports.watch = buildWatch;
|
||||
exports.clean = clean;
|
||||
exports.link = linkUserData;
|
||||
exports.package = packageBuild;
|
||||
exports.update = updateManifest;
|
||||
exports.publish = gulp.series(clean, updateManifest, execBuild, packageBuild, execGit);
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const chalk = require("chalk");
|
||||
const archiver = require("archiver");
|
||||
const stringify = require("json-stringify-pretty-compact");
|
||||
const typescript = require("typescript");
|
||||
|
||||
const ts = require("gulp-typescript");
|
||||
const less = require("gulp-less");
|
||||
const sass = require("gulp-sass");
|
||||
const git = require("gulp-git");
|
||||
|
||||
const argv = require("yargs").argv;
|
||||
|
||||
sass.compiler = require("sass");
|
||||
|
||||
function getConfig() {
|
||||
const configPath = path.resolve(process.cwd(), "foundryconfig.json");
|
||||
let config;
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
config = fs.readJSONSync(configPath);
|
||||
return config;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function getManifest() {
|
||||
const json = {};
|
||||
|
||||
if (fs.existsSync("src")) {
|
||||
json.root = "src";
|
||||
} else {
|
||||
json.root = "dist";
|
||||
}
|
||||
|
||||
const modulePath = path.join(json.root, "module.json");
|
||||
const systemPath = path.join(json.root, "system.json");
|
||||
|
||||
if (fs.existsSync(modulePath)) {
|
||||
json.file = fs.readJSONSync(modulePath);
|
||||
json.name = "module.json";
|
||||
} else if (fs.existsSync(systemPath)) {
|
||||
json.file = fs.readJSONSync(systemPath);
|
||||
json.name = "system.json";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeScript transformers
|
||||
* @returns {typescript.TransformerFactory<typescript.SourceFile>}
|
||||
*/
|
||||
function createTransformer() {
|
||||
/**
|
||||
* @param {typescript.Node} node
|
||||
*/
|
||||
function shouldMutateModuleSpecifier(node) {
|
||||
if (!typescript.isImportDeclaration(node) && !typescript.isExportDeclaration(node)) return false;
|
||||
if (node.moduleSpecifier === undefined) return false;
|
||||
if (!typescript.isStringLiteral(node.moduleSpecifier)) return false;
|
||||
if (!node.moduleSpecifier.text.startsWith("./") && !node.moduleSpecifier.text.startsWith("../")) return false;
|
||||
if (path.extname(node.moduleSpecifier.text) !== "") return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms import/export declarations to append `.js` extension
|
||||
* @param {typescript.TransformationContext} context
|
||||
*/
|
||||
function importTransformer(context) {
|
||||
return (node) => {
|
||||
/**
|
||||
* @param {typescript.Node} node
|
||||
*/
|
||||
function visitor(node) {
|
||||
if (shouldMutateModuleSpecifier(node)) {
|
||||
if (typescript.isImportDeclaration(node)) {
|
||||
const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`);
|
||||
return typescript.updateImportDeclaration(
|
||||
node,
|
||||
node.decorators,
|
||||
node.modifiers,
|
||||
node.importClause,
|
||||
newModuleSpecifier,
|
||||
);
|
||||
} else if (typescript.isExportDeclaration(node)) {
|
||||
const newModuleSpecifier = typescript.createLiteral(`${node.moduleSpecifier.text}.js`);
|
||||
return typescript.updateExportDeclaration(
|
||||
node,
|
||||
node.decorators,
|
||||
node.modifiers,
|
||||
node.exportClause,
|
||||
newModuleSpecifier,
|
||||
);
|
||||
}
|
||||
}
|
||||
return typescript.visitEachChild(node, visitor, context);
|
||||
}
|
||||
|
||||
return typescript.visitNode(node, visitor);
|
||||
};
|
||||
}
|
||||
|
||||
return importTransformer;
|
||||
}
|
||||
|
||||
const tsConfig = ts.createProject("tsconfig.json", {
|
||||
getCustomTransformers: (_program) => ({
|
||||
after: [createTransformer()],
|
||||
}),
|
||||
});
|
||||
|
||||
/********************/
|
||||
/* BUILD */
|
||||
/********************/
|
||||
|
||||
/**
|
||||
* Build TypeScript
|
||||
*/
|
||||
function buildTS() {
|
||||
return gulp.src("src/**/*.ts").pipe(tsConfig()).pipe(gulp.dest("dist"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Less
|
||||
*/
|
||||
function buildLess() {
|
||||
return gulp.src("src/*.less").pipe(less()).pipe(gulp.dest("dist"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SASS
|
||||
*/
|
||||
function buildSASS() {
|
||||
return gulp.src("src/*.scss").pipe(sass().on("error", sass.logError)).pipe(gulp.dest("dist"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy static files
|
||||
*/
|
||||
async function copyFiles() {
|
||||
const statics = ["lang", "fonts", "assets", "templates", "module.json", "system.json", "template.json"];
|
||||
try {
|
||||
for (const file of statics) {
|
||||
if (fs.existsSync(path.join("src", file))) {
|
||||
await fs.copy(path.join("src", file), path.join("dist", file));
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for changes for each build step
|
||||
*/
|
||||
function buildWatch() {
|
||||
gulp.watch("src/**/*.ts", { ignoreInitial: false }, buildTS);
|
||||
gulp.watch("src/**/*.less", { ignoreInitial: false }, buildLess);
|
||||
gulp.watch("src/**/*.scss", { ignoreInitial: false }, buildSASS);
|
||||
gulp.watch(["src/fonts", "src/lang", "src/templates", "src/*.json"], { ignoreInitial: false }, copyFiles);
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* CLEAN */
|
||||
/********************/
|
||||
|
||||
/**
|
||||
* Remove built files from `dist` folder
|
||||
* while ignoring source files
|
||||
*/
|
||||
async function clean() {
|
||||
const name = path.basename(path.resolve("."));
|
||||
const files = [];
|
||||
|
||||
// If the project uses TypeScript
|
||||
if (fs.existsSync(path.join("src", `${name}.ts`))) {
|
||||
files.push(
|
||||
"lang",
|
||||
"templates",
|
||||
"assets",
|
||||
"module",
|
||||
`${name}.js`,
|
||||
"module.json",
|
||||
"system.json",
|
||||
"template.json",
|
||||
);
|
||||
}
|
||||
|
||||
// If the project uses Less or SASS
|
||||
if (fs.existsSync(path.join("src", `${name}.less`)) || fs.existsSync(path.join("src", `${name}.scss`))) {
|
||||
files.push("fonts", `${name}.css`);
|
||||
}
|
||||
|
||||
console.log(" ", chalk.yellow("Files to clean:"));
|
||||
console.log(" ", chalk.blueBright(files.join("\n ")));
|
||||
|
||||
// Attempt to remove the files
|
||||
try {
|
||||
for (const filePath of files) {
|
||||
await fs.remove(path.join("dist", filePath));
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* LINK */
|
||||
/********************/
|
||||
|
||||
/**
|
||||
* Link build to User Data folder
|
||||
*/
|
||||
async function linkUserData() {
|
||||
const name = path.basename(path.resolve("."));
|
||||
const config = fs.readJSONSync("foundryconfig.json");
|
||||
|
||||
let destDir;
|
||||
try {
|
||||
if (
|
||||
fs.existsSync(path.resolve(".", "dist", "module.json")) ||
|
||||
fs.existsSync(path.resolve(".", "src", "module.json"))
|
||||
) {
|
||||
destDir = "modules";
|
||||
} else if (
|
||||
fs.existsSync(path.resolve(".", "dist", "system.json")) ||
|
||||
fs.existsSync(path.resolve(".", "src", "system.json"))
|
||||
) {
|
||||
destDir = "systems";
|
||||
} else {
|
||||
throw Error(`Could not find ${chalk.blueBright("module.json")} or ${chalk.blueBright("system.json")}`);
|
||||
}
|
||||
|
||||
let linkDir;
|
||||
if (config.dataPath) {
|
||||
if (!fs.existsSync(path.join(config.dataPath, "Data")))
|
||||
throw Error("User Data path invalid, no Data directory found");
|
||||
|
||||
linkDir = path.join(config.dataPath, "Data", destDir, name);
|
||||
} else {
|
||||
throw Error("No User Data path defined in foundryconfig.json");
|
||||
}
|
||||
|
||||
if (argv.clean || argv.c) {
|
||||
console.log(chalk.yellow(`Removing build in ${chalk.blueBright(linkDir)}`));
|
||||
|
||||
await fs.remove(linkDir);
|
||||
} else if (!fs.existsSync(linkDir)) {
|
||||
console.log(chalk.green(`Copying build to ${chalk.blueBright(linkDir)}`));
|
||||
await fs.symlink(path.resolve("./dist"), linkDir);
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch (err) {
|
||||
Promise.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* PACKAGE */
|
||||
/*********************/
|
||||
|
||||
/**
|
||||
* Package build
|
||||
*/
|
||||
async function packageBuild() {
|
||||
const manifest = getManifest();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// Remove the package dir without doing anything else
|
||||
if (argv.clean || argv.c) {
|
||||
console.log(chalk.yellow("Removing all packaged files"));
|
||||
fs.removeSync("package");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure there is a directory to hold all the packaged versions
|
||||
fs.ensureDirSync("package");
|
||||
|
||||
// Initialize the zip file
|
||||
const zipName = `${manifest.file.name}-v${manifest.file.version}.zip`;
|
||||
const zipFile = fs.createWriteStream(path.join("package", zipName));
|
||||
const zip = archiver("zip", { zlib: { level: 9 } });
|
||||
|
||||
zipFile.on("close", () => {
|
||||
console.log(chalk.green(zip.pointer() + " total bytes"));
|
||||
console.log(chalk.green(`Zip file ${zipName} has been written`));
|
||||
return resolve();
|
||||
});
|
||||
|
||||
zip.on("error", (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
zip.pipe(zipFile);
|
||||
|
||||
// Add the directory with the final code
|
||||
zip.directory("dist/", manifest.file.name);
|
||||
|
||||
zip.finalize();
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* PACKAGE */
|
||||
/*********************/
|
||||
|
||||
/**
|
||||
* Update version and URLs in the manifest JSON
|
||||
*/
|
||||
function updateManifest(cb) {
|
||||
const packageJson = fs.readJSONSync("package.json");
|
||||
const config = getConfig(),
|
||||
manifest = getManifest(),
|
||||
rawURL = config.rawURL,
|
||||
repoURL = config.repository,
|
||||
manifestRoot = manifest.root;
|
||||
|
||||
if (!config) cb(Error(chalk.red("foundryconfig.json not found")));
|
||||
if (!manifest) cb(Error(chalk.red("Manifest JSON not found")));
|
||||
if (!rawURL || !repoURL) cb(Error(chalk.red("Repository URLs not configured in foundryconfig.json")));
|
||||
|
||||
try {
|
||||
const version = argv.update || argv.u;
|
||||
|
||||
/* Update version */
|
||||
|
||||
const versionMatch = /^(\d{1,}).(\d{1,}).(\d{1,})$/;
|
||||
const currentVersion = manifest.file.version;
|
||||
let targetVersion = "";
|
||||
|
||||
if (!version) {
|
||||
cb(Error("Missing version number"));
|
||||
}
|
||||
|
||||
if (versionMatch.test(version)) {
|
||||
targetVersion = version;
|
||||
} else {
|
||||
targetVersion = currentVersion.replace(versionMatch, (substring, major, minor, patch) => {
|
||||
console.log(substring, Number(major) + 1, Number(minor) + 1, Number(patch) + 1);
|
||||
if (version === "major") {
|
||||
return `${Number(major) + 1}.0.0`;
|
||||
} else if (version === "minor") {
|
||||
return `${major}.${Number(minor) + 1}.0`;
|
||||
} else if (version === "patch") {
|
||||
return `${major}.${minor}.${Number(patch) + 1}`;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (targetVersion === "") {
|
||||
return cb(Error(chalk.red("Error: Incorrect version arguments.")));
|
||||
}
|
||||
|
||||
if (targetVersion === currentVersion) {
|
||||
return cb(Error(chalk.red("Error: Target version is identical to current version.")));
|
||||
}
|
||||
console.log(`Updating version number to '${targetVersion}'`);
|
||||
|
||||
packageJson.version = targetVersion;
|
||||
manifest.file.version = targetVersion;
|
||||
|
||||
/* Update URLs */
|
||||
|
||||
const result = `${rawURL}/v${manifest.file.version}/package/${manifest.file.name}-v${manifest.file.version}.zip`;
|
||||
|
||||
manifest.file.url = repoURL;
|
||||
manifest.file.manifest = `${rawURL}/master/${manifestRoot}/${manifest.name}`;
|
||||
manifest.file.download = result;
|
||||
|
||||
const prettyProjectJson = stringify(manifest.file, {
|
||||
maxLength: 35,
|
||||
indent: "\t",
|
||||
});
|
||||
|
||||
fs.writeJSONSync("package.json", packageJson, { spaces: "\t" });
|
||||
fs.writeFileSync(path.join(manifest.root, manifest.name), prettyProjectJson, "utf8");
|
||||
|
||||
return cb();
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
}
|
||||
|
||||
function gitAdd() {
|
||||
return gulp.src("package").pipe(git.add({ args: "--no-all" }));
|
||||
}
|
||||
|
||||
function gitCommit() {
|
||||
return gulp.src("./*").pipe(
|
||||
git.commit(`v${getManifest().file.version}`, {
|
||||
args: "-a",
|
||||
disableAppendPaths: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function gitTag() {
|
||||
const manifest = getManifest();
|
||||
return git.tag(`v${manifest.file.version}`, `Updated to ${manifest.file.version}`, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
|
||||
const execGit = gulp.series(gitAdd, gitCommit, gitTag);
|
||||
|
||||
const execBuild = gulp.parallel(buildTS, buildLess, buildSASS, copyFiles);
|
||||
|
||||
exports.build = gulp.series(clean, execBuild);
|
||||
exports.watch = buildWatch;
|
||||
exports.clean = clean;
|
||||
exports.link = linkUserData;
|
||||
exports.package = packageBuild;
|
||||
exports.update = updateManifest;
|
||||
exports.publish = gulp.series(clean, updateManifest, execBuild, packageBuild, execGit);
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"gridUnits": "m",
|
||||
"primaryTokenAttribute": "combatValues.hitPoints.current",
|
||||
"url": "https://git.f3l.de/dungeonslayers/ds4",
|
||||
"manifest": "https://git.f3l.de/dungeonslayers/ds4/-/blob/master/src/system.json",
|
||||
"download": "https://git.f3l.de/dungeonslayers/ds4/-/archive/master/ds4-master.zip",
|
||||
"manifest": "https://git.f3l.de/dungeonslayers/ds4/-/raw/master/src/system.json?inline=false",
|
||||
"download": "https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/0.1.0/download?job=build",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue