Use rollup to build typescript

This commit is contained in:
Johannes Loher 2021-03-17 12:15:25 +01:00
parent 79355b6e9c
commit e7fc318ea5
14 changed files with 494 additions and 343 deletions

View file

@ -1,150 +1,58 @@
const gulp = require("gulp");
const fs = require("fs-extra");
const path = require("path");
const chalk = require("chalk");
const stringify = require("json-stringify-pretty-compact");
const typescript = require("typescript");
const ts = require("gulp-typescript");
const sass = require("gulp-sass");
const { rollup } = require("rollup");
const argv = require("yargs").argv;
const chalk = require("chalk");
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");
sass.compiler = require("sass");
function getConfig() {
const configPath = path.resolve(process.cwd(), "foundryconfig.json");
let config;
/********************/
/* CONFIGURATION */
/********************/
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()],
}),
});
const name = path.basename(path.resolve("."));
const sourceDirectory = "./src";
const distDirectory = "./dist";
const stylesDirectory = path.join(sourceDirectory, "scss");
const stylesExtension = ".scss";
const sourceFileExtension = ".ts";
const staticFiles = ["assets", "fonts", "lang", "packs", "templates", "system.json", "template.json"];
const getDownloadURL = (version) =>
`https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/${version}/download?job=build`;
/********************/
/* BUILD */
/* BUILD */
/********************/
/**
* Build TypeScript
* Build the distributable JavaScript code
*/
function buildTS() {
return gulp.src("src/**/*.ts").pipe(tsConfig()).pipe(gulp.dest("dist"));
async function buildCode() {
const build = await rollup({ input: rollupConfig.input, plugins: rollupConfig.plugins });
return build.write(rollupConfig.output);
}
/**
* Build SASS
* Build style sheets
*/
function buildSASS() {
return gulp.src("src/*.scss").pipe(sass().on("error", sass.logError)).pipe(gulp.dest("dist"));
function buildStyles() {
return gulp
.src(path.join(stylesDirectory, `${name}${stylesExtension}`))
.pipe(sass().on("error", sass.logError))
.pipe(gulp.dest(path.join(distDirectory, "css")));
}
/**
* Copy static files
*/
async function copyFiles() {
const statics = ["lang", "fonts", "assets", "templates", "packs", "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));
}
for (const file of staticFiles) {
if (fs.existsSync(path.join(sourceDirectory, file))) {
await fs.copy(path.join(sourceDirectory, file), path.join(distDirectory, file));
}
return Promise.resolve();
} catch (err) {
Promise.reject(err);
}
}
@ -152,180 +60,161 @@ async function copyFiles() {
* Watch for changes for each build step
*/
function buildWatch() {
gulp.watch("src/**/*.ts", { ignoreInitial: false }, buildTS);
gulp.watch("src/**/*.scss", { ignoreInitial: false }, buildSASS);
gulp.watch(
["src/fonts", "src/lang", "src/templates", "src/*.json", "src/packs"],
path.join(sourceDirectory, "**", `*${sourceFileExtension}`).replace(/\\/g, "/"),
{ ignoreInitial: false },
buildCode,
);
gulp.watch(
path.join(stylesDirectory, "**", `*${stylesExtension}`).replace(/\\/g, "/"),
{ ignoreInitial: false },
buildStyles,
);
gulp.watch(
staticFiles.map((file) => path.join(sourceDirectory, file).replace(/\\/g, "/")),
{ ignoreInitial: false },
copyFiles,
);
}
/********************/
/* CLEAN */
/* CLEAN */
/********************/
/**
* Remove built files from `dist` folder
* while ignoring source files
* Remove built files from `dist` folder while ignoring source files
*/
async function clean() {
const name = path.basename(path.resolve("."));
const files = [];
const files = [...staticFiles, "module"];
// If the project uses TypeScript
if (fs.existsSync(path.join("src", "module", `${name}.ts`))) {
files.push(
"lang",
"templates",
"assets",
"module",
"packs",
`${name}.js`,
"module.json",
"system.json",
"template.json",
);
}
// If the project uses SASS
if (fs.existsSync(path.join("src", `${name}.scss`))) {
files.push("fonts", `${name}.css`);
if (fs.existsSync(path.join(stylesDirectory, `${name}${stylesExtension}`))) {
files.push("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);
for (const filePath of files) {
await fs.remove(path.join(distDirectory, filePath));
}
}
/********************/
/* LINK */
/* 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() {
const name = path.basename(path.resolve("."));
const config = fs.readJSONSync("foundryconfig.json");
let destinationDirectory;
if (fs.existsSync(path.resolve(".", sourceDirectory, "system.json"))) {
destinationDirectory = "systems";
} else {
throw new Error(`Could not find ${chalk.blueBright("system.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")}`);
}
const linkDirectory = path.resolve(getDataPath(), destinationDirectory, name);
let linkDir;
if (config.dataPath) {
if (!fs.existsSync(path.join(config.dataPath, "Data")))
throw Error("User Data path invalid, no Data directory found");
if (argv.clean || argv.c) {
console.log(chalk.yellow(`Removing build in ${chalk.blueBright(linkDirectory)}.`));
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);
await fs.remove(linkDirectory);
} else if (!fs.existsSync(linkDirectory)) {
console.log(chalk.green(`Copying build to ${chalk.blueBright(linkDirectory)}.`));
await fs.ensureDir(path.resolve(linkDirectory, ".."));
await fs.symlink(path.resolve(".", distDirectory), linkDirectory);
}
}
/*********************/
/* PACKAGE */
/*********************/
/********************/
/* VERSIONING */
/********************/
/**
* Update version and URLs in the manifest JSON
* Get the contents of the manifest file as object.
*/
function updateManifest(cb) {
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 packageLockJson = fs.existsSync("package-lock.json") ? fs.readJSONSync("package-lock.json") : undefined;
const manifest = getManifest();
if (!manifest) cb(Error(chalk.red("Manifest JSON not found")));
try {
const version = argv.update || argv.u;
const release = argv.release || argv.r;
/* Update version */
const currentVersion = packageJson.version;
const versionMatch = /^(\d{1,}).(\d{1,}).(\d{1,})$/;
const currentVersion = manifest.file.version;
let targetVersion = "";
if (!version) {
cb(Error("Missing version number"));
if (!release) {
return cb(Error("Missing release type"));
}
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 "";
}
});
}
const targetVersion = getTargetVersion(currentVersion, release);
if (targetVersion === "") {
return cb(Error(chalk.red("Error: Incorrect version arguments.")));
if (!targetVersion) {
return cb(new Error(chalk.red("Error: Incorrect version arguments")));
}
if (targetVersion === currentVersion) {
return cb(Error(chalk.red("Error: Target version is identical to current version.")));
return cb(new Error(chalk.red("Error: Target version is identical to current version")));
}
console.log(`Updating version number to '${targetVersion}'`);
packageJson.version = targetVersion;
fs.writeJSONSync("package.json", packageJson, { spaces: 2 });
if (packageLockJson) {
packageLockJson.version = targetVersion;
fs.writeJSONSync("package-lock.json", packageLockJson, { spaces: 2 });
}
manifest.file.version = targetVersion;
/* Update URL */
const result = `https://git.f3l.de/dungeonslayers/ds4/-/jobs/artifacts/${targetVersion}/download?job=build`;
manifest.file.download = result;
const prettyProjectJson =
stringify(manifest.file, {
maxLength: 40,
indent: 4,
}) + "\n";
fs.writeJSONSync("package.json", packageJson, { spaces: 4 });
fs.writeFileSync(path.join(manifest.root, manifest.name), prettyProjectJson, "utf8");
manifest.file.download = getDownloadURL(targetVersion);
fs.writeJSONSync(path.join(sourceDirectory, manifest.name), manifest.file, { spaces: 2 });
return cb();
} catch (err) {
@ -333,10 +222,10 @@ function updateManifest(cb) {
}
}
const execBuild = gulp.parallel(buildTS, buildSASS, copyFiles);
const execBuild = gulp.parallel(buildCode, buildStyles, copyFiles);
exports.build = gulp.series(clean, execBuild);
exports.watch = buildWatch;
exports.clean = clean;
exports.link = linkUserData;
exports.updateManifest = updateManifest;
exports.bumpVersion = bumpVersion;