mantra/gulpfile.ts

481 lines
12 KiB
TypeScript
Raw Normal View History

2021-05-10 23:31:10 +00:00
import { Server, Socket } from "net";
import browserSync = require("browser-sync");
2019-10-07 11:48:01 +00:00
import browserify = require("browserify");
import logger = require("fancy-log");
2021-05-11 19:49:46 +00:00
import { emptyDir, mkdirp, pathExists, remove } from "fs-extra";
import { dest, parallel, series, src, TaskFunction, watch } from "gulp";
2019-10-07 11:48:01 +00:00
import gulpIf = require("gulp-if");
2019-10-07 16:59:23 +00:00
import rename = require("gulp-rename");
import sass = require("gulp-sass");
2019-10-07 11:48:01 +00:00
import terser = require("gulp-terser");
import merge = require("merge-stream");
import minimist = require("minimist");
2022-03-01 15:04:40 +00:00
import * as dartSass from "sass";
2019-10-07 11:48:01 +00:00
import { parseArgsStringToArgv } from "string-argv";
2021-05-11 19:41:05 +00:00
import tsify = require("tsify");
2021-05-11 19:49:46 +00:00
import { changeExt, dirname, join, parse, relative } from "upath";
2019-10-07 11:48:01 +00:00
import buffer = require("vinyl-buffer");
import vinylSourceStream = require("vinyl-source-stream");
import Watchify = require("watchify");
2021-05-11 00:56:38 +00:00
import { Settings } from "./gulp/Settings";
import "./gulp/TaskFunction";
2019-10-07 11:48:01 +00:00
/**
* The port to listen for stop-requests.
*/
const watchConnectorPort = 50958;
/**
* An object for syncing browsers.
*/
let syncer = browserSync.create();
2019-10-07 11:48:01 +00:00
/**
* The message that is printed when starting the compilation in watch mode.
*/
const watchStartMessage = "Starting compilation in watch mode...";
/**
* The message that is printed when starting an incremental compilation.
*/
const incrementalMessage = "File change detected. Starting incremental compilation...";
/**
* Generates the message that is printed after finishing a compilation in watch mode.
*
* @param errorCount
* The number of errors which occurred.
2021-05-10 23:31:10 +00:00
*
* @returns
* The formatted message.
2019-10-07 11:48:01 +00:00
*/
2021-05-10 23:31:10 +00:00
const watchFinishMessage = (errorCount: number): string =>
2019-10-07 11:48:01 +00:00
{
return `Found ${errorCount} errors. Watching for file changes.`;
};
/**
* The arguments passed by the user.
*/
let options = ParseArgs(process.argv.slice(2));
/**
* Parses the specified arguments.
*
* @param args
* The arguments to parse.
2021-05-10 23:31:10 +00:00
*
* @returns
* The parsed arguments.
2019-10-07 11:48:01 +00:00
*/
2021-05-10 23:31:10 +00:00
function ParseArgs(args: string[]): minimist.ParsedArgs
2019-10-07 11:48:01 +00:00
{
return minimist(
args,
{
string: [
"target"
],
alias: {
target: "t"
},
default: {
target: "Debug"
}
});
}
/**
* The settings for building the project.
*/
let settings = new Settings(options["target"]);
/**
2019-10-07 21:32:53 +00:00
* Cleans the project.
*/
2021-05-10 23:31:10 +00:00
export async function Clean(): Promise<void>
{
2019-10-07 15:50:06 +00:00
let directories = [
"javascript",
"css",
"templates",
"assets"
];
for (let directory of directories)
{
2021-05-11 19:49:46 +00:00
await emptyDir(settings.RootPath(directory));
2019-10-07 15:50:06 +00:00
}
2021-05-11 19:49:46 +00:00
if (await pathExists(settings.TestThemePath()))
2019-10-07 15:50:06 +00:00
{
2021-05-11 19:49:46 +00:00
await remove(settings.TestThemePath());
2019-10-07 15:50:06 +00:00
}
2021-05-11 19:49:46 +00:00
await mkdirp(settings.TestThemePath());
await remove(settings.TestThemePath());
2021-05-10 23:31:10 +00:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2019-10-07 20:03:44 +00:00
await require("create-symlink")(settings.RootPath(), settings.TestThemePath(), { type: "junction" });
}
2019-10-07 21:17:44 +00:00
Clean.description = "Cleans the project.";
2019-10-07 11:48:01 +00:00
/**
* Builds the project in watched mode.
2021-05-10 23:31:10 +00:00
*
* @param done
* A callback which is executed once the task has finished.
2019-10-07 11:48:01 +00:00
*/
export let Watch: TaskFunction = (done) =>
{
settings.Watch = true;
Build();
let server = new Server(
(socket) =>
{
socket.on(
"data",
(data) =>
{
let args = parseArgsStringToArgv(data.toString());
socket.destroy();
if (args[0] === "stop")
{
let options = ParseArgs(args.slice(1));
if (options["target"] === settings.Target)
{
syncer.exit();
2019-10-07 11:48:01 +00:00
server.close();
done();
process.exit();
}
}
});
});
server.listen(watchConnectorPort);
};
Watch.description = "Builds the project in watched mode.";
/**
* Reloads all browsers using `browser-sync`.
2021-05-10 23:31:10 +00:00
*
* @param filePath
* A glob-path which points to the files which must be reloaded.
*
* @returns
* The actual task.
*/
function BrowserSync(filePath?: string): TaskFunction
{
let BrowserSync: TaskFunction = (done) =>
{
if (filePath)
{
syncer.reload(filePath);
}
else
{
syncer.reload();
}
done();
};
return BrowserSync;
}
2019-10-07 11:48:01 +00:00
/**
* Builds the project.
*/
2021-05-10 23:31:10 +00:00
export async function Build(): Promise<void>
2019-10-07 11:48:01 +00:00
{
2021-05-11 18:32:34 +00:00
return new Promise(
(resolve, reject) =>
{
if (settings.Watch)
{
syncer.init({
open: false,
proxy: "http://localhost",
port: 3000,
ui: {
port: 3001
},
ghostMode: false,
online: false
});
2021-05-11 19:49:46 +00:00
watch(settings.ThemeSource("**"), { usePolling: true }, series(Theme, BrowserSync("*.css")));
watch(settings.TemplateSource("**"), { usePolling: true }, series(Templates, BrowserSync()));
2021-05-11 18:32:34 +00:00
}
2019-10-07 11:48:01 +00:00
2021-05-11 19:49:46 +00:00
parallel(Library, Theme, Templates)(
2021-05-11 18:32:34 +00:00
(error) =>
{
if (error)
{
reject(error);
}
else
{
resolve();
}
});
});
2019-10-07 11:48:01 +00:00
}
/**
* Builds the TypeScript- and JavaScript-library.
2021-05-10 23:31:10 +00:00
*
* @returns
* The pipeline to execute.
2019-10-07 11:48:01 +00:00
*/
export function Library(): NodeJS.ReadWriteStream
2019-10-07 11:48:01 +00:00
{
let errorMessages: string[] = [];
let streams: NodeJS.ReadWriteStream[] = [];
let queue: NodeJS.ReadWriteStream[] = [];
2019-10-07 11:48:01 +00:00
let tsConfigFile = settings.TypeScriptProjectRoot("tsconfig.json");
2021-05-10 23:31:10 +00:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2019-10-07 11:48:01 +00:00
let tsConfig = require(tsConfigFile);
let optionBase: browserify.Options = {
...Watchify.args,
node: true,
ignoreMissing: true,
debug: settings.Debug
};
let files = (tsConfig.files as string[]).map(
2021-05-11 19:49:46 +00:00
(file) => relative(settings.TypeScriptSourceRoot(), settings.TypeScriptProjectRoot(file)));
2019-10-07 11:48:01 +00:00
if (settings.Watch)
{
logger.info(watchStartMessage);
}
for (let file of files)
{
let builder = (): NodeJS.ReadWriteStream =>
2019-10-07 11:48:01 +00:00
{
let stream: NodeJS.ReadWriteStream;
2019-10-07 11:48:01 +00:00
let bundler = browserify(
{
...optionBase,
2021-05-11 19:49:46 +00:00
basedir: settings.LibraryPath(dirname(file)),
2019-10-07 11:48:01 +00:00
entries: [
settings.TypeScriptSourceRoot(file)
],
2021-05-11 19:49:46 +00:00
standalone: join(dirname(file), parse(file).name)
2019-10-07 11:48:01 +00:00
});
if (settings.Watch)
{
bundler = Watchify(bundler, { poll: true });
2019-10-07 11:48:01 +00:00
}
bundler.plugin(
2021-05-11 19:41:05 +00:00
tsify,
2019-10-07 11:48:01 +00:00
{
project: tsConfigFile
});
stream = bundler.bundle().on(
"error",
(error) =>
{
let message: string = error.message;
if (!errorMessages.includes(message))
2019-10-07 11:48:01 +00:00
{
let result = new RegExp(`^(${error["fileName"]})\\((\\d+|\\d+(,\\d+){1,3})\\): .* TS([\\d]+): (.*)$`).exec(message);
errorMessages.push(message);
2021-05-11 19:49:46 +00:00
console.log(`${relative(process.cwd(), result[1])}(${result[2]}): ${result[4]} ${result[5]}`);
}
}
).pipe(
2021-05-11 19:49:46 +00:00
vinylSourceStream(changeExt(file, "js"))
).pipe(
buffer()
).pipe(
gulpIf(
!settings.Debug,
terser()
)
).pipe(
2021-05-11 19:49:46 +00:00
dest(settings.LibraryPath())
).on(
"end",
() =>
{
if (settings.Watch)
{
if (queue.includes(stream))
{
queue.splice(queue.indexOf(stream), 1);
}
if (queue.length === 0)
{
logger.info(watchFinishMessage(errorMessages.length));
if (errorMessages.length === 0)
2019-10-07 11:48:01 +00:00
{
syncer.reload("*.js");
2019-10-07 11:48:01 +00:00
}
errorMessages.splice(0, errorMessages.length);
}
}
});
2019-10-07 11:48:01 +00:00
if (settings.Watch)
{
bundler.once(
2019-10-07 11:48:01 +00:00
"update",
() =>
{
console.log(`Update called for ${file}: ${queue.length}`);
if (queue.length === 0)
2019-10-07 11:48:01 +00:00
{
logger.info(incrementalMessage);
2019-10-07 11:48:01 +00:00
}
queue.push(builder());
2019-10-07 11:48:01 +00:00
});
}
return stream;
};
let stream = builder();
queue.push(stream);
streams.push(stream);
2019-10-07 11:48:01 +00:00
}
return merge(streams);
2019-10-07 11:48:01 +00:00
}
Library.description = "Builds the TypeScript- and JavaScript-library.";
2019-10-07 16:59:23 +00:00
/**
* Builds the theme.
2021-05-10 23:31:10 +00:00
*
* @returns
* The pipeline to execute.
2019-10-07 16:59:23 +00:00
*/
export function Theme(): NodeJS.ReadWriteStream
2019-10-07 16:59:23 +00:00
{
2021-05-10 20:46:32 +00:00
if (settings.Watch)
{
2021-05-11 19:55:14 +00:00
logger.info("Building scss-code.");
2021-05-10 20:46:32 +00:00
}
2021-05-11 19:49:46 +00:00
return src(
2019-10-07 16:59:23 +00:00
settings.ThemeSource("main.scss"),
{
sourcemaps: settings.Debug,
base: settings.StylePath()
}).pipe(
2022-03-01 15:04:40 +00:00
sass(dartSass).sync(
2019-10-07 18:53:26 +00:00
{
2021-05-10 20:46:32 +00:00
importer: require("node-sass-tilde-importer")
2019-11-22 13:14:49 +00:00
}
).on("error",
(error) =>
{
2021-05-10 20:46:32 +00:00
console.log(
JSON.stringify(
{
status: error.status,
file: error.file,
line: error.line,
column: error.column,
message: error.messageOriginal,
formatted: error.formatted
},
null,
4));
2019-10-07 18:53:26 +00:00
})
2019-10-07 16:59:23 +00:00
).pipe(
rename(
(parsedPath) =>
{
parsedPath.dirname = "";
parsedPath.basename = "mantra";
})
).pipe(
2021-05-11 19:49:46 +00:00
dest(
2019-10-07 16:59:23 +00:00
settings.StylePath(),
settings.Debug ?
{
sourcemaps: true
2021-05-10 23:31:10 +00:00
} :
undefined)
2021-05-10 20:46:32 +00:00
).on(
"end",
() =>
{
if (settings.Watch)
{
2021-05-11 19:55:14 +00:00
logger.info("Building scss-code finished.");
2021-05-10 20:46:32 +00:00
}
});
2019-10-07 16:59:23 +00:00
}
Theme.description = "Builds the theme.";
2019-10-07 17:09:35 +00:00
/**
* Builds the templates.
2021-05-10 23:31:10 +00:00
*
* @returns
* The pipeline to execute.
2019-10-07 17:09:35 +00:00
*/
2021-05-10 23:31:10 +00:00
export function Templates(): NodeJS.ReadWriteStream
2019-10-07 17:09:35 +00:00
{
2021-05-11 19:49:46 +00:00
return src(
2019-10-07 17:09:35 +00:00
settings.TemplateSource("**")).pipe(
2021-05-11 19:49:46 +00:00
dest(settings.TemplatePath()));
2019-10-07 17:09:35 +00:00
}
Templates.description = "Builds the templates.";
2019-10-07 11:48:01 +00:00
/**
* Stops a watch-task.
*/
2021-05-10 23:31:10 +00:00
export async function Stop(): Promise<void>
2019-10-07 11:48:01 +00:00
{
try
{
await new Promise(
(resolve, reject) =>
{
let client = new Socket();
client.connect(
watchConnectorPort,
"localhost",
async () =>
{
client.write(`stop -t ${settings.Target}`);
});
client.on("close", resolve);
client.on("error", reject);
});
}
catch
{
logger.info("The specified task is not running.");
2019-10-07 11:48:01 +00:00
}
}
2021-05-10 23:31:10 +00:00
Stop.description = "Stops a watch-task";