UNPKG

@sfdx-falcon/generator

Version:

Extends Yeoman's Generator class, adding customized support for SFDX-Falcon specific tools and capabilities. Part of the SFDX-Falcon Library.

423 lines 27.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); //─────────────────────────────────────────────────────────────────────────────────────────────────┐ /** * @author Vivek M. Chawla <@VivekMChawla> * @copyright 2019, Vivek M. Chawla / Salesforce. All rights reserved. * @license BSD-3-Clause For full license text, see the LICENSE file in the repo root or * `https://opensource.org/licenses/BSD-3-Clause` * @file packages/generator/src/generator.ts * @summary Exports `SfdxFalconGenerator` for use with custom Yeoman generators. * @description Exports an abstract class that extends Yeoman's `Generator` class, adding * customized support for SFDX-Falcon specific tools and capabilities. */ //─────────────────────────────────────────────────────────────────────────────────────────────────┘ // Import External Libraries, Modules, and Types const Generator = require("yeoman-generator"); // Class. Custom Generator classes must extend this. // Import SFDX-Falcon Libraries const util_1 = require("@sfdx-falcon/util"); // Library. Helper functions for building and showing banners to the user. const validator_1 = require("@sfdx-falcon/validator"); // Library of Type Validation helper functions. // Import SFDX-Falcon Classes & Functions const builder_1 = require("@sfdx-falcon/builder"); // Class. Collection of key data structures that represent the overall context of the external environment inside of which some a set of specialized logic will be run. const debug_1 = require("@sfdx-falcon/debug"); // Class. Provides custom "debugging" services (ie. debug-style info to console.log()). const environment_1 = require("@sfdx-falcon/environment"); // Class. Provides custom "debugging" services (ie. debug-style info to console.log()). const error_1 = require("@sfdx-falcon/error"); // Class. Extends SfdxError to provide specialized error structures for SFDX-Falcon modules. const status_1 = require("@sfdx-falcon/status"); // Class. Implements a framework for creating results-driven, informational objects with a concept of heredity (child results) and the ability to "bubble up" both Errors (thrown exceptions) and application-defined "failures". const status_2 = require("@sfdx-falcon/status"); // Function. Prints a Styled Message to the console using Chalk. const status_3 = require("@sfdx-falcon/status"); // Class. Status tracking object for use with Yeoman Generators. const types_1 = require("@sfdx-falcon/types"); // Enum. Represents the various types/states of a Status Message. // Set the File Local Debug Namespace const dbgNs = '@sfdx-falcon:generator'; debug_1.SfdxFalconDebug.msg(`${dbgNs}:`, `Debugging initialized for ${dbgNs}`); //─────────────────────────────────────────────────────────────────────────────────────────────────┐ /** * @class SfdxFalconGenerator * @extends Generator * @summary Abstract base class class for building Yeoman Generators for SFDX-Falcon commands. * @description Classes that extend `SfdxFalconGenerator` must provide a type parameter to * ensure that the "answers" family of member variables (`defaultAnswers`, * `userAnswers`, `metaAnswers`, and `finalAnswers`) has the appropriate interface * type which defines the answers that are relevant to a concrete child class. * @public @abstract */ //─────────────────────────────────────────────────────────────────────────────────────────────────┘ class SfdxFalconGenerator extends Generator { //───────────────────────────────────────────────────────────────────────────┐ /** * @constructs SfdxFalconGenerator * @param {string|string[]} args Required. Array of arguments that are * passed to this generator by Yeoman if the generator is being * invoked from the command line. Passed directly through to * the superclass constructor without modification. * @param {GeneratorOptions} opts Required. Object containing options * that help specify how a specific generator is run. These * options are set when external code uses a Yeoman Environment * to `run()` a Generator that's derived from this class. * @description Constructs an `SfdxFalconGenerator` object. * @public */ //───────────────────────────────────────────────────────────────────────────┘ constructor(args, opts, reqs) { // Define function-local debug namespace and debug incoming arguments. const funcName = `constructor`; const dbgNsLocal = `${dbgNs}:${funcName}`; debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:arguments:`, arguments); // Validate core options. validator_1.TypeValidator.throwOnEmptyNullInvalidObject(reqs, `${dbgNsLocal}`, `GeneratorRequirements`); validator_1.TypeValidator.throwOnNullInvalidObject(reqs.gitEnvReqs, `${dbgNsLocal}`, `GeneratorRequirements.gitEnvReqs`); validator_1.TypeValidator.throwOnNullInvalidObject(reqs.localEnvReqs, `${dbgNsLocal}`, `GeneratorRequirements.localEnvReqs`); validator_1.TypeValidator.throwOnNullInvalidObject(reqs.sfdxEnvReqs, `${dbgNsLocal}`, `GeneratorRequirements.sfdxEnvReqs`); validator_1.TypeValidator.throwOnEmptyNullInvalidObject(opts, `${dbgNsLocal}`, `GeneratorOptions`); validator_1.TypeValidator.throwOnEmptyNullInvalidObject(opts.packageJson, `${dbgNsLocal}`, `GeneratorOptions.packageJson`); validator_1.TypeValidator.throwOnEmptyNullInvalidString(opts.commandName, `${dbgNsLocal}`, `GeneratorOptions.commandName`); validator_1.TypeValidator.throwOnEmptyNullInvalidString(opts.generatorType, `${dbgNsLocal}`, `GeneratorOptions.generatorType`); validator_1.TypeValidator.throwOnEmptyNullInvalidString(opts.generatorPath, `${dbgNsLocal}`, `GeneratorOptions.generatorPath`); validator_1.TypeValidator.throwOnNullInvalidInstance(opts.generatorResult, status_1.SfdxFalconResult, `${dbgNsLocal}`, `GeneratorOptions.generatorType`); // Call the parent constructor to initialize the Yeoman Generator. super(args, opts); // Resolve Generator Requirements const generatorReqs = { sfdxEnvReqs: { standardOrgs: (reqs.sfdxEnvReqs.standardOrgs === true ? true : false), scratchOrgs: (reqs.sfdxEnvReqs.scratchOrgs === true ? true : false), devHubOrgs: (reqs.sfdxEnvReqs.devHubOrgs === true ? true : false), envHubOrgs: (reqs.sfdxEnvReqs.envHubOrgs === true ? true : false), managedPkgOrgs: (reqs.sfdxEnvReqs.managedPkgOrgs === true ? true : false), unmanagedPkgOrgs: (reqs.sfdxEnvReqs.unmanagedPkgOrgs === true ? true : false) }, gitEnvReqs: { // TODO: Implement once Git Environment is fleshed out. }, localEnvReqs: { // TODO: Implement once Local Environment is fleshed out. } }; // Attempt to pull the VERSION key from package.json. const version = opts.packageJson['version']; const pkgVersion = validator_1.TypeValidator.isNotEmptyNullInvalidString(version) ? version : `??.??.??`; debug_1.SfdxFalconDebug.str(`${dbgNsLocal}:pkgVersion:`, pkgVersion); // Attempt to pull the FALCON key from package.json. const falcon = opts.packageJson['falcon']; const pkgFalcon = validator_1.TypeValidator.isNotNullInvalidObject(falcon) ? falcon : {}; debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:pkgFalcon:`, pkgFalcon); // Initialize class members. this.commandName = opts.commandName; // Name of the command that's executing the Generator (eg. 'falcon:adk:clone'). this.dbgNs = this.constructor.name; // Initial debug namespace. By default, the name of the derived class. this.generatorType = opts.generatorType; // Type (ie. file name minus the .ts extension) of the Generator being run. this.generatorResult = opts.generatorResult; // Used for activity tracking and communication back to the calling command. this.generatorReqs = generatorReqs; // Generator Requirements. Should be modified by the derived class. this.generatorStatus = new status_3.GeneratorStatus(); // Tracks status and build messages to the user. this.packageJson = opts.packageJson; // Refers to the package manifest (package.json) of the module that implements the command that's executing this generator. this.pluginVersion = pkgVersion; // Version of the plugin, taken from package.json. this.falcon = pkgFalcon; // Falcon global JsonMap, taken from package.json. this.sharedData = {}; // Special context for sharing data between Generator, Inquirer Questions, and Listr Tasks. // Initialize the External Context. this.extCtx = new builder_1.ExternalContext({ dbgNs: this.dbgNs, context: this, generatorStatus: this.generatorStatus, parentResult: this.generatorResult, sharedData: this.sharedData }); // Initialize all Run-Loop Status booleans to `null`. // In practice, `null` will mean an unset value, `false` means failure, `true` means success. this.runLoopStatus = { initializingComplete: null, promptingComplete: null, configuringComplete: null, writingComplete: null, installComplete: null, endComplete: null }; // Initialize all Answer objects. this.answers = { default: {}, final: {}, user: {}, meta: {}, confirmation: { proceed: null, restart: null, abort: null } }; // Set defaults for all Generator messages. this.generatorMessages = { opening: `SFDX-Falcon Powered Plugin\n${this.commandName}\nv${this.pluginVersion}`, preInterview: `Starting Interview...`, confirmation: `Would you like to proceed based on the above settings?`, postInterview: ``, success: `${this.commandName} completed successfully`, failure: `${this.commandName} exited without completing the expected tasks`, warning: `${this.commandName} completed successfully, but with some warnings (see above)` }; // Start the GeneratorStatus object and add it to the detail of the GENERATOR Result. this.generatorStatus.start(); this.generatorResult.setDetail({ commandName: this.commandName, generatorType: this.generatorType, generatorPath: this.generatorPath, generatorReqs: this.generatorReqs, generatorMsgs: this.generatorMessages, interviewAnswers: this.answers, runLoopStatus: this.runLoopStatus, generatorStatus: this.generatorStatus }); this.generatorResult.debugResult(`After setting Detail in constructor`, `${dbgNsLocal}`); // Initialize Shared Data. this.sharedData['commandName'] = this.commandName; this.sharedData['generatorRequirements'] = this.generatorReqs; this.sharedData['generatorStatus'] = this.generatorStatus; } //───────────────────────────────────────────────────────────────────────────┐ /** * @method _showOpener * @returns {Promise<void>} * @description Shows an opening message when the `initializing` run-loop * function is executed. Uses the string from * `this.generatorMessage.opening` as the source of the message * contents. Can be overridden by derived class to customize the * opener behavior. * @protected @async */ //───────────────────────────────────────────────────────────────────────────┘ async _showOpener() { // Use the SFDX-Falcon Banner Utility to show the opening message. console.error(util_1.BannerUtil.buildBanner(this.generatorMessages.opening)); } //───────────────────────────────────────────────────────────────────────────┐ /** * @method __initializing * @returns {Promise<void>} * @description STEP ONE in the Yeoman run-loop. Intended to be executed as * part of Yeoman's `initializing` run-loop priority. Will call * the matching single-underscore method `_initializing()` from * the derived class after executing logic that's specialized * for `SfdxFalconGenerator` based Generators. This method must * be called by the `initializing()` method that's implemented * by the dervived class. * @protected @async */ //───────────────────────────────────────────────────────────────────────────┘ async __initializing() { // Define function-local debug namespace. const funcName = `__initializing`; const dbgNsLocal = `${this.dbgNs}:${funcName}`; // Do nothing if the Generator has been aborted. if (this.generatorStatus.aborted) { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `Generator has been aborted.`); return; } // Show the Yeoman to announce that the generator is running. await this._showOpener(); // Execute the initialization tasks for this generator try { // SFDX Environment Initialization this.sfdxEnv = await environment_1.SfdxEnvironment.initialize({ requirements: this.generatorReqs.sfdxEnvReqs, dbgNs: this.dbgNs, verbose: true, silent: false }); // TODO: Git Initialization // TODO: Local Initialization } catch (initializationError) { debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:initializationError:`, initializationError); // Add an "abort" item to the Generator Status object. this.generatorStatus.abort({ type: types_1.StatusMessageType.ERROR, title: 'Initialization Error', message: `${this.commandName} command aborted because one or more initialization tasks failed` }); // Throw an Initialization Error. throw new error_1.SfdxFalconError(`Command initialization failed. ${initializationError.message}`, `InitializationError`, `${dbgNs}:default_initializing`, error_1.SfdxFalconError.wrap(initializationError)); } // Add a line break to separate this section from the next in the console. console.log(''); // End by calling the matching "single-underscore" run-loop method from the derived class. return await this._initializing(); } //───────────────────────────────────────────────────────────────────────────┐ /** * @method __prompting * @returns {Promise<void>} * @description STEP TWO in the Yeoman run-loop. Interviews the User to get * information needed by the `writing` and `installing` phases. * Intended to be executed as part of Yeoman's `prompting` * run-loop priority. Will call the matching single-underscore * method `_prompting()` from the derived class after executing * logic that's specialized for `SfdxFalconGenerator` based * Generators. This method must be called by the `prompting()` * method that's implemented by the dervived class. * @protected @async */ //───────────────────────────────────────────────────────────────────────────┘ async __prompting() { // Define function-local debug namespace. const funcName = `__prompting`; const dbgNsLocal = `${this.dbgNs}:${funcName}`; // Do nothing if the Generator has been aborted. if (this.generatorStatus.aborted) { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `Generator has been aborted.`); return; } // Show the pre-interview message. status_2.printStyledMessage({ message: this.generatorMessages.preInterview, styling: `yellow` }); // Build the User Interview. this.userInterview = this._buildInterview(); // Start the User Interview. this.answers.final = await this.userInterview.start(); // Extract the "User Answers" from the Interview for inclusion in the GENERATOR Result's detail. this.generatorResult.detail['userAnswers'] = this.userInterview.userAnswers; // Check if the user aborted the Interview. if (this.userInterview.status.aborted) { this.generatorStatus.abort({ type: types_1.StatusMessageType.ERROR, title: 'Command Aborted', message: `${this.commandName} canceled by user. ${this.userInterview.status.reason}` }); } // Show the post-interview message. status_2.printStyledMessage({ message: this.generatorMessages.postInterview, styling: `yellow` }); // End by calling the matching "single-underscore" run-loop method from the derived class. return await this._prompting(); } //───────────────────────────────────────────────────────────────────────────┐ /** * @method __configuring * @returns {void} * @description STEP THREE in the Yeoman run-loop. Perform any pre-install * configuration steps based on the answers provided by the User. * Intended to be executed as part of Yeoman's `configuring` * run-loop priority. Will call the matching single-underscore * method `_configuring()` from the derived class after executing * logic that's specialized for `SfdxFalconGenerator` based * Generators. this method must be called by the `configuring()` * method that's implemented by the dervived class. * @protected @async */ //───────────────────────────────────────────────────────────────────────────┘ async __configuring() { // Define function-local debug namespace. const funcName = `__configuring`; const dbgNsLocal = `${this.dbgNs}:${funcName}`; // Do nothing if the Generator has been aborted. if (this.generatorStatus.aborted) { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `Generator has been aborted.`); return; } // End by calling the matching "single-underscore" run-loop method from the derived class. return await this._configuring(); } //───────────────────────────────────────────────────────────────────────────┐ /** * @method __writing * @returns {Promise<void>} * @description __STEP FOUR in the Yeoman run-loop.__ Typically, this is where * you perform filesystem writes, git clone operations, etc. * Intended to be executed as part of Yeoman's `writing` run-loop * priority. Will call the matching single-underscore method * `_writing()` from the derived class after executing logic * that's specialized for `SfdxFalconGenerator` based Generators. * This method must] be called by the `writing()` method that's * implemented by the dervived class. * @protected @async */ //───────────────────────────────────────────────────────────────────────────┘ async __writing() { // Define function-local debug namespace. const funcName = `__writing`; const dbgNsLocal = `${this.dbgNs}:${funcName}`; // Do nothing if the Generator has been aborted. if (this.generatorStatus.aborted) { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `Generator has been aborted.`); return; } // End by calling the matching "single-underscore" run-loop method from the derived class. return await this._writing(); } //───────────────────────────────────────────────────────────────────────────┐ /** * @method install * @returns {Promise<void>} * @description __STEP FIVE in the Yeoman run-loop.__ Typically, this is where * you perform operations that must happen AFTER files are * written to disk. For example, if the `writing` step downloaded * an app to install, the `install` step would run the * installation. Intended to be executed as part of Yeoman's * `install` run-loop priority. Will call the matching * single-underscore method `_install()` from the derived class * after executing logic that's specialized for `SfdxFalconGenerator` * based Generators. This method must be called by the `install()` * method that's implemented by the dervived class. * @protected @async */ //───────────────────────────────────────────────────────────────────────────┘ async __install() { // Define function-local debug namespace. const funcName = `__install`; const dbgNsLocal = `${this.dbgNs}:${funcName}`; // Do nothing if the Generator has been aborted. if (this.generatorStatus.aborted) { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `Generator has been aborted.`); return; } // End by calling the matching "single-underscore" run-loop method from the derived class. return await this._install(); } //───────────────────────────────────────────────────────────────────────────┐ /** * @method __end * @returns {Promise<void>} * @description __STEP SIX in the Yeoman run-loop.__ This is the FINAL step * that Yeoman runs and it gives us a chance to do any post-Yeoman * updates and/or cleanup. Intended to be executed as part of the * `end` run-loop priority. Will call the matching single-underscore * method `_end()` from the derived class after executing logic * that's specialized for `SfdxFalconGenerator` based Generators. * This method must be called by the `end()` method that's * implemented by the dervived class. * @protected @async */ //───────────────────────────────────────────────────────────────────────────┘ async __end() { // Define function-local debug namespace. const funcName = `__end`; const dbgNsLocal = `${this.dbgNs}:${funcName}`; // Check if the Yeoman interview/installation process was aborted. if (this.generatorStatus.aborted) { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `Generator has been aborted.`); // Add a final error message this.generatorStatus.addMessage({ type: types_1.StatusMessageType.ERROR, title: 'Command Failed', message: `${this.generatorMessages.failure}\n` }); } else { // Generator completed successfully. Final message depends on presence of Generator Status Warnings. this.generatorStatus.complete([ { type: types_1.StatusMessageType.SUCCESS, title: 'Command Succeded', message: this.generatorStatus.hasWarning ? `${this.generatorMessages.warning}\n` : `${this.generatorMessages.success}\n` } ]); } // Print the final status table. this.generatorStatus.printStatusMessages(); // End by calling the matching "single-underscore" run-loop method from the derived class. return await this._end(); } } exports.SfdxFalconGenerator = SfdxFalconGenerator; //# sourceMappingURL=generator.js.map