@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
JavaScript
"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