import { isObject, isString, merge, get } from 'lodash'; import { MonoRepoPackage, MrPackageConfig as MrPackageConfigCore } from '@cirrusct/mr-core'; import { PackageConfigSettings } from '@cirrusct/config'; import { createError } from '@cirrusct/core-node'; import { Logger } from '@cirrusct/logging'; import { BuildSpecConfig, BuildConfig, MrPackageConfig, BuildSpecConfigBuilder, MrBuildConfigBuilder, BuildPathsConfig, TypescriptTranspilerConfigOptions, MrBuildConfigProfiles, } from './buildConfig'; import { BuildSpecBuilderFactory, MrBuilderBuildOptions, IPackageBuilder } from './builder'; const DefaultProfileName = 'lib'; export class MrBuild { constructor() {} public static get defaultProfileName(): string { return DefaultProfileName; } private static _buildSpecHandlerFactories: { [name: string]: BuildSpecBuilderFactory } = {}; private static _buildConfigs: MrBuildConfigBuilder = new MrBuildConfigBuilder(DefaultProfileName); public static registerHandlerFactories = (handlers: { [name: string]: BuildSpecBuilderFactory }) => { for (const key of Object.keys(handlers)) { MrBuild._buildSpecHandlerFactories[key] = handlers[key]; } }; public static get buildSpecHandlerFactories(): { [name: string]: BuildSpecBuilderFactory } { return MrBuild._buildSpecHandlerFactories; } public static createSpecBuilder = async ( pkg: MonoRepoPackage, logger: Logger, buildConfig: BuildConfig, spec: BuildSpecConfig, options: MrBuilderBuildOptions ): Promise => { const specBuilder = spec ? spec.builder || spec.builder : 'lib'; const factory = MrBuild.buildSpecHandlerFactories[specBuilder]; if (!factory) { throw createError(`SpecBuilder Factory not found for type '${specBuilder}'`); } try { logger.debug({ specBuilder }, 'Running BuilderFactory'); return await factory(pkg, logger, buildConfig, spec, options); } catch (e) { throw createError('SpecBuilder Factory failed', e); } }; public static createSpecBuilders = async ( mrPackage: MonoRepoPackage, logger: Logger, options: MrBuilderBuildOptions ): Promise<{ [name: string]: IPackageBuilder }> => { const specBuilders = {}; const buildConfig = mrPackage.config.commands.build; if (buildConfig) { if (!buildConfig.ignore) { const createBuilder = async (specName: string) => { const spec = buildConfig.specs[specName]; const specBuilder = await MrBuild.createSpecBuilder( mrPackage, logger, buildConfig, buildConfig.specs[specName], options ); specBuilders[specName] = specBuilder; }; // determine which spec(s) to build // get spec name(s) from options, or then from defaultSpecs prop on build config const selectedSpecs = options.specs || buildConfig.defaultSpecs; // convert selected spec names to array const selectedSpecNames = selectedSpecs ? Array.isArray(selectedSpecs) ? selectedSpecs : [selectedSpecs] : []; // if no spec(s) specified in options or on buildConfig.. if (selectedSpecNames.length === 0) { // array of all spec names defined in buildConfig const allNames = Object.keys(buildConfig.specs); // look first for spec named 'default', then fall back to the first defined spec selectedSpecNames.push(allNames.indexOf('default') >= 0 ? 'default' : allNames[0]); } else { logger.debug( { selectedSpecNames, }, 'Building specs specified in config or options' ); } // loop through specs defined based on above priority, add create builder for (const specName of selectedSpecNames) { await createBuilder(specName); } } } else { throw createError(`No Build Config defined for package '${mrPackage.name}'`); } return specBuilders; }; public static get buildConfigs(): MrBuildConfigBuilder { return MrBuild._buildConfigs; } public static get profiles(): MrBuildConfigProfiles { return MrBuild.buildConfigs.profiles; } public static getPackageBuildConfig = async ( mrPackage: MonoRepoPackage ): Promise => { return await MrBuild.buildConfigs.getPackageBuildConfig(mrPackage); }; public static cleanPackage = async ( mrPackage: MonoRepoPackage, logger: Logger, options: MrBuilderBuildOptions = {} ) => { const buildConfig = mrPackage.config.commands.build; if (buildConfig && buildConfig.ignore) { return Promise.resolve(); } logger.debug(`Cleaning Package: '${mrPackage.name}'`); try { const specBuilders = await MrBuild.createSpecBuilders( mrPackage as MonoRepoPackage, logger, options ); for (const specKey in specBuilders) { const spec = buildConfig.specs[specKey]; const specLogger = logger.child({ spec: specKey, builder: spec.builder, profile: spec.profile, }); specLogger.debug('Cleaning Spec'); await specBuilders[specKey].clean(); specLogger.debug('Spec Build Cleaned Successfully'); } } catch (err) { logger.fatal({ err }, 'Clean Failed'); throw createError('Package Clean failed', err); } }; public static buildPackage = async ( mrPackage: MonoRepoPackage, logger: Logger, options: MrBuilderBuildOptions = {} ) => { const buildConfig = mrPackage.config.commands.build; if (buildConfig && buildConfig.ignore) { return Promise.resolve(); } logger.info(`Building Package: '${mrPackage.name}'`); try { const specBuilders = await MrBuild.createSpecBuilders( mrPackage as MonoRepoPackage, logger, options ); for (const specKey in specBuilders) { const spec = buildConfig.specs[specKey]; logger.info({ spec: specKey, builder: spec.builder, profile: spec.profile }, 'Building Spec'); await specBuilders[specKey].build(); logger.debug('Spec Build Completed Successfully'); } } catch (err) { logger.fatal({ err }, 'Build Failed'); throw createError('Package Build failed', err); } }; public static startPackage = async ( mrPackage: MonoRepoPackage, logger: Logger, options: MrBuilderBuildOptions = {} ) => { const buildConfig = mrPackage.config.commands.build; if (buildConfig && buildConfig.ignore) { return Promise.resolve(); } logger.info(`Starting Package: '${mrPackage.name}'`); try { const specBuilders = await MrBuild.createSpecBuilders( mrPackage as MonoRepoPackage, logger, options ); for (const builderName in specBuilders) { const specBuilder = specBuilders[builderName]; if (specBuilder.isStartable) { logger.info({ specBuilder: builderName }, 'Starting Spec'); await specBuilder.start(); logger.debug({ specBuilder: builderName }, 'Spec Started Successfully'); } else { throw createError(`Spec '${builderName}' for package '${mrPackage.name}' is not startable`); } } } catch (err) { logger.fatal({ err }, 'Start Failed'); throw createError('Package Start failed', err); } }; }