UNPKG

gulptraum

Version:

Smart task generator for gulp to get a build pipeline and more in seconds.

392 lines (258 loc) 10.3 kB
'use strict'; import * as path from 'path'; import * as merge from 'deepmerge'; import {DefaultBuildSystemConfig} from './index'; import {exec} from 'child_process'; import * as vorpal from 'vorpal'; import * as clone from 'clone'; import * as gulp from 'gulp'; import { IPluginConfiguration, IGulptraumPlugin, IGulpVersionAdapter, ITaskConfiguration, ICliTaskArguments, IConventionalTaskConfiguration, IGroupedPluginKeys, IBuildSystem, IBuildSystemConfiguration } from './index'; import {GulpV3Adapter, GulpV4Adapter} from './adapters/index'; import * as systemTasks from './tasks/index'; function mergeArray(destinationArray: Array<any>, sourceArray: Array<any>, mergeOptions) { return sourceArray; } const mergeOptions = { arrayMerge: mergeArray, }; export class BuildSystem implements IBuildSystem { public pluginConfigs: Map<string, IPluginConfiguration> = new Map<string, IPluginConfiguration>(); public plugins: Map<string, IGulptraumPlugin> = new Map<string, IGulptraumPlugin>(); public gulpAdapter: IGulpVersionAdapter; public gulp: any; public config: IBuildSystemConfiguration = {}; public cli: any; constructor(config: IBuildSystemConfiguration) { this.config = config; this.initialize(); } get tasks() { return this.gulpAdapter.getGulpTasks(); } public initialize(): void { this.cli = vorpal(); } public registerTasks(externalGulp?: any): void { this.gulp = externalGulp || gulp; //eslint-disable-line this._initializeGulpVersionAdapter(); this.config = this._mergeConfigs(DefaultBuildSystemConfig, this.config); this._validateBuildSystemConfig(this.config); this._registerTasksBeforePlugins(); this._initializePlugins(); this._registerTasksAfterPlugins(); this._registerSystemTasks(); } private _initializeGulpVersionAdapter(): void { const isVersion3 = typeof Object.getPrototypeOf(this.gulp).run !== 'undefined'; if (isVersion3) { this.gulpAdapter = new GulpV3Adapter(this.gulp, this); } else { this.gulpAdapter = new GulpV4Adapter(this.gulp, this); } } private _validateBuildSystemConfig(config: IBuildSystemConfiguration): void { if (!config.packageName) { try { const packageManifestPath = path.resolve(`${config.paths.root}/package.json`); const packageManifest = require(packageManifestPath); if (packageManifest) { config.fullPackageName = packageManifest.name; let name = packageManifest.name; if (name[0] == '@') { name = name.slice(1); } config.packageName = name; } } catch (error) { console.log(error); } } } private _registerTasksBeforePlugins(): void { } private _registerSystemTasks(): void { const systemTasks = this._getSystemTasks(); systemTasks.forEach((systemTask) => { this._registerSystemTask(systemTask); }); } private _registerSystemTask(taskName): void { const task = this._getSystemTask(taskName); if (!task.excludeTaskFromCli) { this._registerTaskToCli(task.name, task.help); } task.generate(this.gulp, this.config, this); } private _getSystemTasks(): Array<string> { return Object.keys(systemTasks); } private _getSystemTask(taskName): any { return systemTasks[taskName]; } private _initializePlugins(): void { const pluginsToInitialize = this._getPluginKeysOrderedByPriority(); pluginsToInitialize.forEach((plugin) => { this._initializePlugin(plugin); }); } private _initializePlugin(name): void { const plugin = this._getPlugin(name); const configUsed = this._getResolvedPluginConfig(name); plugin.initializePlugin(this.gulp, configUsed, this); } private _registerTasksAfterPlugins(): void { this._registerConventionalTasks(); } private _registerConventionalTasks(): void { const conventionalTasks = Object.keys(this.config.conventionalTasks); conventionalTasks.forEach((conventionalTask) => { this._registerConventionalTask(conventionalTask); }); } private _getTasksInRunningOrder(pluginKeys: Array<string>): Array<string> { const pluginKeysGroupedByPriority = this._getPluginKeysGroupedByPriority(); const groupKeys = Object.keys(pluginKeysGroupedByPriority); const groupKeysSorted = groupKeys.sort((a: string, b: string) => { return +b - +a; }); const allTasks = []; groupKeysSorted.forEach((key) => { const tasks = pluginKeysGroupedByPriority[key]; allTasks.push(tasks); }); return allTasks; } private _getTaskNameByConvention(taskName: string, pluginKey: string): string { return `${taskName}-${pluginKey}`; } private _getBuildTasksForConventionalTask(taskName: string): Array<Array<string>> { const pluginKeys = this._getPluginKeysOrderedByPriority(); const tasksInRunningOrder = this._getTasksInRunningOrder(pluginKeys); const topLevelTasks = tasksInRunningOrder.map((pluginKey) => { if (Array.isArray(pluginKey)) { // these are only one level deep and are used to differentiate // between tasks executed sequentially or in parallel return pluginKey.map((subKey) => { return this._getTaskNameByConvention(taskName, subKey); }); } return [this._getTaskNameByConvention(taskName, pluginKey)]; }); const tasksRegistered = topLevelTasks.map((subTasks) => { return this._filterUnregisteredTasks(subTasks); }); return tasksRegistered; } private _filterUnregisteredTasks(tasks: Array<string>): Array<string> { const tasksRegistered = tasks.filter((task) => { return this.gulpAdapter.isTaskRegistered(task); }); return tasksRegistered; } public task(taskName: string, config: ITaskConfiguration, taskCallback: Function): void { const help = config.help || 'no help provided'; this._registerTaskToCli(taskName, help); return this.gulpAdapter.registerGulpTask(taskName, taskCallback); } private _registerTaskToCli(taskName: string, help: string): void { this.cli.command(taskName, help) .action((args, callback) => { return this._runTaskFromCli(taskName, args, callback); }); } private _runTaskFromCli(taskName: string, args: ICliTaskArguments, callback: Function): void { const optionKeys = Object.keys(args.options); const optionStrings = optionKeys.map((optionKey) => { const optionValue = args.options[optionKey]; return `--${optionKey} ${optionValue}`; }); if (optionKeys.length === 0) { return this._runCommandInChildProcess(`gulp ${taskName}`, callback); } const gulpCommand = ['gulp', taskName].concat(optionStrings) .join(' ') .trim(); this._runCommandInChildProcess(gulpCommand, callback); } private _runCommandInChildProcess(command: string, callback: Function): void { const commandCallback = (error, stdout, stderr) => { console.log(stdout); callback(); } const execOptions: any = { shell: true, }; exec(command, execOptions, commandCallback); } private _registerConventionalTaskToCli(taskName: string): void { const help = this._getHelpForConventionalTask(taskName); this._registerTaskToCli(taskName, help); } private _registerConventionalTask(taskName: string): void { this._registerConventionalTaskToCli(taskName); const taskConfig = this._getConventionalTaskConfig(taskName); const buildTasks = this._getBuildTasksForConventionalTask(taskName); this.gulpAdapter.registerConventionalTask(taskName, taskConfig, buildTasks); } private _getHelpForConventionalTask(taskName: string): string { const taskConfig = this._getConventionalTaskConfig(taskName); return taskConfig.help || 'help not found'; } private _getConventionalTaskConfig(taskName: string): IConventionalTaskConfiguration { return this.config.conventionalTasks[taskName]; } private _getPluginKeysGroupedByPriority(): IGroupedPluginKeys { const allPluginKeys = this._getPluginKeys(); const groupedPluginKeys: IGroupedPluginKeys = {}; for (const pluginkey of allPluginKeys) { const config = this._getPluginConfig(pluginkey); const groupHasMatchingEntry: boolean = groupedPluginKeys[config.priority] !== undefined; if (groupHasMatchingEntry) { groupedPluginKeys[config.priority].push(pluginkey); } else { groupedPluginKeys[config.priority] = [pluginkey]; } } return groupedPluginKeys; } private _getPluginKeys(): Array<string> { return Object.keys(this.pluginConfigs); } private _getPlugin(name: string): IGulptraumPlugin { return this.plugins[name]; } private _getPluginConfig(name: string): IPluginConfiguration { return this.pluginConfigs[name]; } private _getPluginDefaultConfig(name: string): IPluginConfiguration { if (!this.plugins[name]) { throw new Error(`Default configuration for plugin "${name}" is missing.`); } const config = clone(this.config); return this.plugins[name].getDefaultConfig(config); } private _getResolvedPluginConfig(name: string): IPluginConfiguration { const pluginConfig = this._getPluginConfig(name); const pluginDefaultConfig = this._getPluginDefaultConfig(name); const resolvedConfig = this._mergeConfigs(pluginDefaultConfig, pluginConfig); return resolvedConfig; } private _getPluginKeysOrderedByPriority(): Array<string> { const allPluginKeys = this._getPluginKeys(); const sortedPluginKeys = allPluginKeys.sort((a, b) => { const pluginAConfig = this._getPluginConfig(a); const pluginBConfig = this._getPluginConfig(b); return pluginBConfig.priority - pluginAConfig.priority; }); return sortedPluginKeys; } private _mergeConfigs(defaultConfig: any, config: any): any { return merge(defaultConfig, config, mergeOptions); } public registerPlugin(name: string, plugin: IGulptraumPlugin, config: IPluginConfiguration, priority: number = 10): IBuildSystem { this.plugins[name] = plugin; this.pluginConfigs[name] = config; this.pluginConfigs[name].priority = priority; return this; } }