src/services/cli/cliSHBuild.js
- const { provider } = require('jimple');
- const CLICommand = require('../../abstracts/cliCommand');
- /**
- * This is the _'real build command'_. This is a private command the shell script executes in order
- * to get a list of commands to run.
- * @extends {CLICommand}
- * @todo This whole class needs a refactor (args and params are the same!).
- */
- class CLISHBuildCommand extends CLICommand {
- /**
- * Class constructor.
- * @param {Builder} builder Needed to generate a target
- * build command.
- * @param {CLICleanCommand} cliCleanCommand Needed to generate the command
- * that cleans a target files.
- * @param {CLICopyProjectFilesCommand} cliCopyProjectFilesCommand Needed to generate the command
- * to copy the project files if
- * the feature of copying on
- * build is enabled.
- * @param {CLIRevisionCommand} cliRevisionCommand Needed to generate the command
- * that creates the revision file
- * if the feature of generating
- * it on build is enabled.
- * @param {CLISHCopyCommand} cliSHCopyCommand Needed to generate the command
- * to copy the target files if
- * the target doesn't require
- * bundling.
- * @param {CLISHNodeRunCommand} cliSHNodeRunCommand Needed to generate the command
- * to run a Node target if the
- * `run` option is used.
- * @param {CLISHNodeWatchCommand} cliSHNodeWatchCommand Needed to generate the command
- * to watch a Node target files
- * if the `watch` option is used.
- * @param {CLISHTranspileCommand} cliSHTranspileCommand Needed to generate the command
- * to transpile a Node target
- * code.
- * @param {Events} events Used to reduce the list of
- * commands generated.
- * @param {ProjectConfigurationSettings} projectConfiguration Used to read and validate the
- * features.
- * @param {Targets} targets Used to get the targets
- * information.
- */
- constructor(
- builder,
- buildTypeScriptHelper,
- cliCleanCommand,
- cliCopyProjectFilesCommand,
- cliRevisionCommand,
- cliSHCopyCommand,
- cliSHNodeRunCommand,
- cliSHNodeWatchCommand,
- cliSHTranspileCommand,
- events,
- projectConfiguration,
- targets
- ) {
- super();
- /**
- * A local reference for the `builder` service.
- * @type {Builder}
- */
- this.builder = builder;
- /**
- * A local reference for the `buildTypeScriptHelper` service.
- * @type {BuildTypeScriptHelper}
- */
- this.buildTypeScriptHelper = buildTypeScriptHelper;
- /**
- * A local reference for the `cliCleanCommand` service.
- * @type {CliCleanCommand}
- */
- this.cliCleanCommand = cliCleanCommand;
- /**
- * A local reference for the `cliCopyProjectFilesCommand` service.
- * @type {CliCopyProjectFilesCommand}
- */
- this.cliCopyProjectFilesCommand = cliCopyProjectFilesCommand;
- /**
- * A local reference for the `cliRevisionCommand` service.
- * @type {CliRevisionCommand}
- */
- this.cliRevisionCommand = cliRevisionCommand;
- /**
- * A local reference for the `cliSHCopyCommand` service.
- * @type {CliSHCopyCommand}
- */
- this.cliSHCopyCommand = cliSHCopyCommand;
- /**
- * A local reference for the `cliSHNodeRunCommand` service.
- * @type {CliSHNodeRunCommand}
- */
- this.cliSHNodeRunCommand = cliSHNodeRunCommand;
- /**
- * A local reference for the `cliSHNodeWatchCommand` service.
- * @type {CliSHNodeWatchCommand}
- */
- this.cliSHNodeWatchCommand = cliSHNodeWatchCommand;
- /**
- * A local reference for the `cliSHTranspileCommand` service.
- * @type {CliSHTranspileCommand}
- */
- this.cliSHTranspileCommand = cliSHTranspileCommand;
- /**
- * A local reference for the `events` service.
- * @type {Events}
- */
- this.events = events;
- /**
- * All the project settings.
- * @type {ProjectConfigurationSettings}
- */
- this.projectConfiguration = projectConfiguration;
- /**
- * A local reference for the `targets` service.
- * @type {Targets}
- */
- this.targets = targets;
- /**
- * The instruction needed to trigger the command.
- * @type {string}
- */
- this.command = 'sh-build [target]';
- /**
- * A description of the command, just to follow the interface as the command won't show up on
- * the help interface.
- * @type {string}
- */
- this.description = 'Get the build commands for the shell program to execute';
- this.addOption(
- 'type',
- '-t, --type [type]',
- 'Which build type: development (default) or production',
- 'development'
- );
- this.addOption(
- 'run',
- '-r, --run',
- 'Run the target after the build is completed. It only works when the ' +
- 'build type is development',
- false
- );
- this.addOption(
- 'watch',
- '-w, --watch',
- 'Rebuild the target every time one of its files changes. It only works ' +
- 'when the build type is development',
- false
- );
- this.addOption(
- 'inspect',
- '-i, --inspect',
- 'Enables the Node inspector. It only works when running Node targets',
- false
- );
- this.addOption(
- 'analyze',
- '-a, --analyze',
- 'Enables the bundle analyzer. It only works with targets with bundling',
- false
- );
- /**
- * Hide the command from the help interface.
- * @type {boolean}
- */
- this.hidden = true;
- /**
- * Enable unknown options so other services can customize the build command.
- * @type {boolean}
- */
- this.allowUnknownOptions = true;
- }
- /**
- * Handle the execution of the command and outputs the list of commands to run.
- * This method emits the event reducer `build-target-commands-list` with the list of commands,
- * the target information, the type of build and whether or not the target should be executed;
- * and it expects a list of commands on return.
- * @param {?string} name The name of the target.
- * @param {Command} command The executed command (sent by `commander`).
- * @param {CLIBuildCommandOptions} options The command options.
- * @param {Object} unknownOptions A dictionary of extra options that command may
- * have received.
- */
- handle(name, command, options, unknownOptions) {
- const { type } = options;
- // Get the target information
- const target = name ?
- // If the target doesn't exist, this will throw an error.
- this.targets.getTarget(name) :
- // Get the default target or throw an error if the project doesn't have targets.
- this.targets.getDefaultTarget();
-
- const {
- development,
- analyze,
- run,
- inspect,
- watch,
- } = this._normalizeOptions(options, target);
- /**
- * Check whether or not a build will be created. This is always `true` for browser targets, but
- * it can be `false` for Node targets if bundling and transpiling is disabled.
- */
- let build = true;
- if (target.is.node) {
- build = !development || target.bundle || target.transpile;
- }
- // Define the parameters object to send to the other methods.
- const params = {
- target,
- type,
- run,
- build,
- watch,
- inspect,
- analyze,
- };
-
- // Based on the target type, get the list of commands.
- const commands = target.is.node ?
- this._getCommandsForNodeTarget(params) :
- this._getCommandsForBrowserTarget(params);
- // Reduce the list of commands.
- const output = this.events.reduce(
- 'build-target-commands-list',
- commands.filter((cmd) => !!cmd),
- params,
- unknownOptions
- )
- // Join the commands on a single string.
- .join(';');
- // Outputs all the commands
- this.output(output);
- }
- /**
- * Normalizes the options received by the command in order to resolve "impossible combinations",
- * like trying to analyze a target that is not for bundling or trying to inspect a browser
- * target.
- * @param {CLIBuildCommandOptions} options The command options.
- * @param {Target} target The target information.
- * @return {CLIBuildCommandNormalizedOptions}
- * @access protected
- * @ignore
- */
- _normalizeOptions(options, target) {
- const development = options.type === 'development';
- // Check if there's a reason to analyze the target bundle.
- const analyze = options.analyze && (target.is.browser || target.bundle);
- // Check if there's a reason for the target to be executed.
- const run = !analyze && development && (target.runOnDevelopment || options.run);
- // Check if there's a reason for the Node inspector to be enabled.
- const inspect = run && target.is.node && (target.inspect.enabled || options.inspect);
- // Check if the target files should be watched.
- const watch = !run && (target.watch[options.type] || options.watch);
-
- return {
- development,
- analyze,
- run,
- inspect,
- watch,
- };
- }
- /**
- * Get the build (and run) commands for a Node target.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {Array}
- * @access protected
- * @ignore
- */
- _getCommandsForNodeTarget(params) {
- // Get the base commands.
- const commands = [
- this._getCleanCommandIfNeeded(params),
- this._getBuildCommandIfNeeded(params),
- this._getCopyCommandIfNeeded(params),
- this._getTranspileCommandIfNeeded(params),
- this._getTypeScriptDeclarationsCommandIfNeeded(params),
- ];
- // If the target won't be executed nor their files will be watched...
- if (!params.run && !params.watch) {
- // ...push the commands to create the revision file and copy the project files.
- commands.push(...[
- this._getRevisionCommandIfNeeded(params),
- this._getCopyProjectFilesCommand(params),
- ]);
- } else if (!params.target.bundle) {
- /**
- * If the target will be executed or their files will be watched, and is not a bundled target,
- * push the command to either run or watch. The reason it's handled this ways is because if
- * the target is bundled, the build engine will take care of the execution/watch.
- */
- if (params.run) {
- // Run the target with `nodemon`.
- commands.push(this._getNodeRunCommand(params));
- } else if (params.type === 'production' || params.target.transpile) {
- // Watch the target with `watchpack`.
- commands.push(this._getNodeWatchCommand(params));
- }
- }
-
- return commands;
- }
- /**
- * Get the build (and run) commands for a browser target.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {Array}
- * @access protected
- * @ignore
- */
- _getCommandsForBrowserTarget(params) {
- // Get the base commands.
- const commands = [
- this._getCleanCommandIfNeeded(params),
- this._getBuildCommandIfNeeded(params),
- this._getTypeScriptDeclarationsCommandIfNeeded(params),
- ];
- // If the target won't be executed...
- if (!params.run && !params.watch) {
- // ...push the commands to create the revision file and copy the project files.
- commands.push(...[
- this._getRevisionCommandIfNeeded(params),
- this._getCopyProjectFilesCommand(params),
- ]);
- }
-
- return commands;
- }
- /**
- * Get the command to remove the previous build files of a target, but only if the target will be
- * build, otherwise, it will return an empty string.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getCleanCommandIfNeeded(params) {
- let command = '';
- if (params.build && params.target.cleanBeforeBuild) {
- command = this.cliCleanCommand.generate({
- target: params.target.name,
- });
- }
-
- return command;
- }
- /**
- * Get the command to actually build a target.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getBuildCommandIfNeeded(params) {
- return this.builder.getTargetBuildCommand(
- params.target,
- params.type,
- params.run,
- params.watch,
- params.inspect,
- params.analyze
- );
- }
- /**
- * Get the command to copy a target files, but only if the target will be _'build'_ (transpiled
- * counts) and it doesn't support bundling, otherwise, it will return an empty string.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getCopyCommandIfNeeded(params) {
- let command = '';
- if (params.build && !params.target.bundle) {
- command = this.cliSHCopyCommand.generate({
- target: params.target.name,
- type: params.type,
- });
- }
-
- return command;
- }
- /**
- * Get the command to transpile a target files, but only if the target will be _'build'_
- * (transpiled counts) and it doesn't support bundling, otherwise, it will return an empty string.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getTranspileCommandIfNeeded(params) {
- let command = '';
- if (params.build && !params.target.bundle) {
- command = this.cliSHTranspileCommand.generate({
- target: params.target.name,
- type: params.type,
- });
- }
-
- return command;
- }
- /**
- * Get the command to generate a TypeScript target type declarations, but only if the target
- * uses TypeScript, won't run and won't be watched: The idea is to generate the declarations only
- * when you build the target and not during all the development process.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getTypeScriptDeclarationsCommandIfNeeded(params) {
- let command = '';
- if (
- params.target.typeScript &&
- params.build &&
- !params.run &&
- !params.watch
- ) {
- command = this.buildTypeScriptHelper.getDeclarationsCommand(params.target);
- }
-
- return command;
- }
- /**
- * Get the command to run a Node target.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getNodeRunCommand(params) {
- return this.cliSHNodeRunCommand.generate({
- target: params.target.name,
- inspect: params.inspect,
- });
- }
- /**
- * Get the command to watch a Node target.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getNodeWatchCommand(params) {
- return this.cliSHNodeWatchCommand.generate({
- target: params.target.name,
- });
- }
- /**
- * Get the command to create the revision file, but only if the feature is enabled, otherwise,
- * it will return an empty string.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getRevisionCommandIfNeeded(params) {
- const {
- enabled,
- createRevisionOnBuild,
- } = this.projectConfiguration.version.revision;
- let command = '';
- if (enabled && createRevisionOnBuild.enabled) {
- const revisionEnvCheck = !createRevisionOnBuild.onlyOnProduction ||
- (createRevisionOnBuild.onlyOnProduction && params.type === 'production');
- const revisionTargetCheck = !createRevisionOnBuild.targets.length ||
- createRevisionOnBuild.targets.includes(params.target.name);
-
- if (revisionEnvCheck && revisionTargetCheck) {
- command = this.cliRevisionCommand.generate();
- }
- }
-
- return command;
- }
- /**
- * Get the command to copy the project files, but only if the feature is enabled, otherwise,
- * it will return an empty string.
- * @param {CLIBuildCommandParams} params A dictionary with all the required information the
- * service needs to run the command: The target
- * information, the build type, whether or not the target
- * will be executed, etc.
- * @return {string}
- * @access protected
- * @ignore
- */
- _getCopyProjectFilesCommand(params) {
- const { enabled, copyOnBuild } = this.projectConfiguration.copy;
- let command = '';
- if (enabled && copyOnBuild.enabled) {
- const copyEnvCheck = !copyOnBuild.onlyOnProduction ||
- (copyOnBuild.onlyOnProduction && params.type === 'production');
- const copyTargetCheck = !copyOnBuild.targets.length ||
- copyOnBuild.targets.includes(params.target.name);
-
- if (copyEnvCheck && copyTargetCheck) {
- command = this.cliCopyProjectFilesCommand.generate();
- }
- }
-
- return command;
- }
- }
- /**
- * The service provider that once registered on the app container will set an instance of
- * `CLISHBuildCommand` as the `cliSHBuildCommand` service.
- * @example
- * // Register it on the container
- * container.register(cliSHBuildCommand);
- * // Getting access to the service instance
- * const cliSHBuildCommand = container.get('cliSHBuildCommand');
- * @type {Provider}
- */
- const cliSHBuildCommand = provider((app) => {
- app.set('cliSHBuildCommand', () => new CLISHBuildCommand(
- app.get('builder'),
- app.get('buildTypeScriptHelper'),
- app.get('cliCleanCommand'),
- app.get('cliCopyProjectFilesCommand'),
- app.get('cliRevisionCommand'),
- app.get('cliSHCopyCommand'),
- app.get('cliSHNodeRunCommand'),
- app.get('cliSHNodeWatchCommand'),
- app.get('cliSHTranspileCommand'),
- app.get('events'),
- app.get('projectConfiguration').getConfig(),
- app.get('targets')
- ));
- });
-
- module.exports = {
- CLISHBuildCommand,
- cliSHBuildCommand,
- };