- 1 :
'use strict';
- 2 :
const fs = require('fs');
- 3 :
const path = require('path');
- 4 :
const EventEmitter = require('events');
- 5 :
const chalk = require('chalk');
- 6 :
const _ = require('lodash');
- 7 :
const GroupedQueue = require('grouped-queue');
- 8 :
const escapeStrRe = require('escape-string-regexp');
- 9 :
const untildify = require('untildify');
- 10 :
const memFs = require('mem-fs');
- 11 :
const FileEditor = require('mem-fs-editor');
- 12 :
const debug = require('debug')('yeoman:environment');
- 13 :
const isScoped = require('is-scoped');
- 14 :
const crypto = require('crypto');
- 15 :
const npmlog = require('npmlog');
- 16 :
const {TrackerGroup} = require('are-we-there-yet');
- 17 :
- 18 :
const {promisify} = require('util');
- 19 :
const {pipeline: _pipeline} = require('stream');
- 20 :
const pipeline = promisify(_pipeline);
- 21 :
- 22 :
const ENVIRONMENT_VERSION = require('../package.json').version;
- 23 :
const Store = require('./store');
- 24 :
const composability = require('./composability');
- 25 :
const resolver = require('./resolver');
- 26 :
const TerminalAdapter = require('./adapter');
- 27 :
const YeomanRepository = require('./util/repository');
- 28 :
const Conflicter = require('./util/conflicter');
- 29 :
const {YeomanCommand} = require('./util/command');
- 30 :
const {
- 31 :
createCommitTransform,
- 32 :
createConflicterCheckTransform,
- 33 :
createConflicterStatusTransform,
- 34 :
createEachFileTransform,
- 35 :
createModifiedTransform,
- 36 :
createYoRcTransform,
- 37 :
createYoResolveTransform
- 38 :
} = require('./util/transform');
- 39 :
- 40 :
/**
- 41 :
* Two-step argument splitting function that first splits arguments in quotes,
- 42 :
* and then splits up the remaining arguments if they are not part of a quote.
- 43 :
*/
- 44 :
function splitArgsFromString(argsString) {
- 45 :
let result = [];
- 46 :
if (!argsString) {
- 47 :
return result;
- 48 :
}
- 49 :
const quoteSeparatedArgs = argsString.split(/("[^"]*")/).filter(x => x);
- 50 :
quoteSeparatedArgs.forEach(arg => {
- 51 :
if (arg.match('\x22')) {
- 52 :
result.push(arg.replace(/"/g, ''));
- 53 :
} else {
- 54 :
result = result.concat(arg.trim().split(' '));
- 55 :
}
- 56 :
});
- 57 :
return result;
- 58 :
}
- 59 :
- 60 :
/**
- 61 :
* Hint of generator module name
- 62 :
*/
- 63 :
function getGeneratorHint(namespace) {
- 64 :
if (isScoped(namespace)) {
- 65 :
const splitName = namespace.split('/');
- 66 :
return `${splitName[0]}/generator-${splitName[1]}`;
- 67 :
}
- 68 :
return `generator-${namespace}`;
- 69 :
}
- 70 :
- 71 :
const mixins = [
- 72 :
require('./command.js'),
- 73 :
require('./generator-features.js'),
- 74 :
require('./namespace'),
- 75 :
require('./package-manager.js')
- 76 :
];
- 77 :
- 78 :
const Base = mixins.reduce((a, b) => b(a), EventEmitter);
- 79 :
- 80 :
class Environment extends Base {
- 81 :
static get UNKNOWN_NAMESPACE() {
- 82 :
return 'unknownnamespace';
- 83 :
}
- 84 :
- 85 :
static get UNKNOWN_RESOLVED() {
- 86 :
return 'unknown';
- 87 :
}
- 88 :
- 89 :
static get queues() {
- 90 :
return [
- 91 :
'environment:run',
- 92 :
'initializing',
- 93 :
'prompting',
- 94 :
'configuring',
- 95 :
'default',
- 96 :
'writing',
- 97 :
'transform',
- 98 :
'conflicts',
- 99 :
'environment:conflicts',
- 100 :
'install',
- 101 :
'end'
- 102 :
];
- 103 :
}
- 104 :
- 105 :
static get lookups() {
- 106 :
return ['.', 'generators', 'lib/generators', 'dist/generators'];
- 107 :
}
- 108 :
- 109 :
/**
- 110 :
* Make sure the Environment present expected methods if an old version is
- 111 :
* passed to a Generator.
- 112 :
* @param {Environment} env
- 113 :
* @return {Environment} The updated env
- 114 :
*/
- 115 :
static enforceUpdate(env) {
- 116 :
if (!env.adapter) {
- 117 :
env.adapter = new TerminalAdapter();
- 118 :
}
- 119 :
- 120 :
if (!env.runLoop) {
- 121 :
env.runLoop = new GroupedQueue(Environment.queues, false);
- 122 :
}
- 123 :
- 124 :
if (!env.sharedFs) {
- 125 :
env.sharedFs = memFs.create();
- 126 :
}
- 127 :
- 128 :
if (!env.fs) {
- 129 :
env.fs = FileEditor.create(env.sharedFs);
- 130 :
}
- 131 :
- 132 :
return env;
- 133 :
}
- 134 :
- 135 :
/**
- 136 :
* Prepare a commander instance for cli support.
- 137 :
*
- 138 :
* @param {Class} GeneratorClass - Generator to create Command
- 139 :
* @return {Command} Return a Command instance
- 140 :
*/
- 141 :
static prepareCommand(GeneratorClass, command = new YeomanCommand()) {
- 142 :
command = Base.addEnvironmentOptions(command);
- 143 :
return Environment.prepareGeneratorCommand(command, GeneratorClass);
- 144 :
}
- 145 :
- 146 :
/**
- 147 :
* Prepare a commander instance for cli support.
- 148 :
*
- 149 :
* @param {Command} command - Command to be prepared
- 150 :
* @param {Class} GeneratorClass - Generator to create Command
- 151 :
* @return {Command} return command
- 152 :
*/
- 153 :
static prepareGeneratorCommand(command, GeneratorClass) {
- 154 :
const generator = new GeneratorClass([], {help: true, env: {}});
- 155 :
Base.addGeneratorOptions(command, generator);
- 156 :
- 157 :
command.action(async function () {
- 158 :
command.env = Environment.createEnv(this.opts());
- 159 :
- 160 :
let rootCommand = this;
- 161 :
while (rootCommand.parent) {
- 162 :
rootCommand = rootCommand.parent;
- 163 :
}
- 164 :
rootCommand.emit('yeoman:environment', command.env);
- 165 :
- 166 :
const generator = command.env.instantiate(GeneratorClass, this.args, this.opts());
- 167 :
await command.env.queueGenerator(generator);
- 168 :
return command.env.start().then(() => command.env);
- 169 :
});
- 170 :
return command;
- 171 :
}
- 172 :
- 173 :
/**
- 174 :
* Factory method to create an environment instance. Take same parameters as the
- 175 :
* Environment constructor.
- 176 :
*
- 177 :
* @deprecated @param {string[]} [args] - arguments.
- 178 :
* @param {object} [options] - Environment options.
- 179 :
* @param {Adapter} [adapter] - Terminal adapter.
- 180 :
*
- 181 :
* @return {Environment} a new Environment instance
- 182 :
*/
- 183 :
static createEnv(args, options, adapter) {
- 184 :
if (args && !Array.isArray(args)) {
- 185 :
options = args;
- 186 :
}
- 187 :
options = options || {};
- 188 :
return new Environment(options, adapter);
- 189 :
}
- 190 :
- 191 :
/**
- 192 :
* Factory method to create an environment instance. Take same parameters as the
- 193 :
* Environment constructor.
- 194 :
*
- 195 :
* @param {String} version - Version of the Environment
- 196 :
* @param {...any} args - Same arguments as {@link Environment}#createEnv.
- 197 :
* @return {Environment} a new Environment instance
- 198 :
*/
- 199 :
static async createEnvWithVersion(version, ...args) {
- 200 :
const repository = new YeomanRepository();
- 201 :
const installedVersion = repository.verifyInstalledVersion('yeoman-environment', version);
- 202 :
if (!installedVersion) {
- 203 :
await repository.installPackage('yeoman-environment', version);
- 204 :
}
- 205 :
const VersionedEnvironment = repository.requireModule('yeoman-environment', version);
- 206 :
return VersionedEnvironment.createEnv(...args);
- 207 :
}
- 208 :
- 209 :
/**
- 210 :
* Convert a generators namespace to its name
- 211 :
*
- 212 :
* @param {String} namespace
- 213 :
* @return {String}
- 214 :
*/
- 215 :
static namespaceToName(namespace) {
- 216 :
return namespace.split(':')[0];
- 217 :
}
- 218 :
- 219 :
/**
- 220 :
* Lookup for a specific generator.
- 221 :
*
- 222 :
* @param {String} namespace
- 223 :
* @param {Object} [options]
- 224 :
* @param {Boolean} [options.localOnly=false] - Set true to skip lookups of
- 225 :
* globally-installed generators.
- 226 :
* @param {Boolean} [options.packagePath=false] - Set true to return the package
- 227 :
* path instead of generators file.
- 228 :
* @param {Boolean} [options.singleResult=true] - Set false to return multiple values.
- 229 :
* @return {String} generator
- 230 :
*/
- 231 :
static lookupGenerator(namespace, options) {
- 232 :
options = typeof options === 'boolean' ? {singleResult: true, localOnly: options} : {singleResult: !(options && options.multiple), ...options};
- 233 :
- 234 :
options.filePatterns = options.filePatterns || Environment.lookups.map(prefix => path.join(prefix, '*/index.{js,ts}'));
- 235 :
- 236 :
const name = Environment.namespaceToName(namespace);
- 237 :
options.packagePatterns = options.packagePatterns || getGeneratorHint(name);
- 238 :
const envProt = Environment.prototype;
- 239 :
- 240 :
options.npmPaths = options.npmPaths || envProt.getNpmPaths(options.localOnly).reverse();
- 241 :
options.packagePatterns = options.packagePatterns || 'generator-*';
- 242 :
options.packagePaths = options.packagePaths || resolver.packageLookup.findPackagesIn(options.npmPaths, options.packagePatterns);
- 243 :
- 244 :
let paths = options.singleResult ? undefined : [];
- 245 :
resolver.packageLookup.sync(options, module => {
- 246 :
const filename = module.filePath;
- 247 :
const fileNS = envProt.namespace(filename, Environment.lookups);
- 248 :
if (namespace === fileNS || (options.packagePath && namespace === Environment.namespaceToName(fileNS))) {
- 249 :
// Version 2.6.0 returned pattern instead of modulePath for options.packagePath
- 250 :
const returnPath = options.packagePath ? module.packagePath : (options.generatorPath ? path.posix.join(filename, '../../') : filename);
- 251 :
if (options.singleResult) {
- 252 :
paths = returnPath;
- 253 :
return true;
- 254 :
}
- 255 :
paths.push(returnPath);
- 256 :
}
- 257 :
return false;
- 258 :
});
- 259 :
- 260 :
return paths;
- 261 :
}
- 262 :
- 263 :
/**
- 264 :
* @classdesc `Environment` object is responsible of handling the lifecyle and bootstrap
- 265 :
* of generators in a specific environment (your app).
- 266 :
*
- 267 :
* It provides a high-level API to create and run generators, as well as further
- 268 :
* tuning where and how a generator is resolved.
- 269 :
*
- 270 :
* An environment is created using a list of `arguments` and a Hash of
- 271 :
* `options`. Usually, this is the list of arguments you get back from your CLI
- 272 :
* options parser.
- 273 :
*
- 274 :
* An optional adapter can be passed to provide interaction in non-CLI environment
- 275 :
* (e.g. IDE plugins), otherwise a `TerminalAdapter` is instantiated by default
- 276 :
*
- 277 :
* @constructor
- 278 :
* @mixes env/resolver
- 279 :
* @mixes env/composability
- 280 :
* @param {String|Array} args
- 281 :
* @param {Object} opts
- 282 :
* @param {Boolean} [opts.experimental]
- 283 :
* @param {Object} [opts.sharedOptions]
- 284 :
* @param {Console} [opts.console]
- 285 :
* @param {Stream} [opts.stdin]
- 286 :
* @param {Stream} [opts.stdout]
- 287 :
* @param {Stream} [opts.stderr]
- 288 :
* @param {TerminalAdapter} [adapter] - A TerminalAdapter instance or another object
- 289 :
* implementing this adapter interface. This is how
- 290 :
* you'd interface Yeoman with a GUI or an editor.
- 291 :
*/
- 292 :
constructor(options, adapter) {
- 293 :
super();
- 294 :
- 295 :
this.setMaxListeners(100);
- 296 :
- 297 :
this.options = options || {};
- 298 :
this.adapter = adapter || new TerminalAdapter({console: this.options.console, stdin: this.options.stdin, stderr: this.options.stderr});
- 299 :
this.cwd = this.options.cwd || process.cwd();
- 300 :
this.cwd = path.resolve(this.cwd);
- 301 :
this.logCwd = this.options.logCwd || this.cwd;
- 302 :
this.store = new Store();
- 303 :
this.command = this.options.command;
- 304 :
- 305 :
this.runLoop = new GroupedQueue(Environment.queues, false);
- 306 :
this.sharedFs = memFs.create();
- 307 :
- 308 :
// Each composed generator might set listeners on these shared resources. Let's make sure
- 309 :
// Node won't complain about event listeners leaks.
- 310 :
this.runLoop.setMaxListeners(0);
- 311 :
this.sharedFs.setMaxListeners(0);
- 312 :
- 313 :
// Create a shared mem-fs-editor instance.
- 314 :
this.fs = FileEditor.create(this.sharedFs);
- 315 :
- 316 :
this.lookups = Environment.lookups;
- 317 :
this.aliases = [];
- 318 :
- 319 :
this.alias(/^([^:]+)$/, '$1:app');
- 320 :
- 321 :
// Used sharedOptions from options if exists.
- 322 :
this.sharedOptions = this.options.sharedOptions || {};
- 323 :
// Remove Unecessary sharedOptions from options
- 324 :
delete this.options.sharedOptions;
- 325 :
- 326 :
// Create a default sharedData.
- 327 :
this.sharedOptions.sharedData = this.sharedOptions.sharedData || {};
- 328 :
- 329 :
// Pass forwardErrorToEnvironment to generators.
- 330 :
this.sharedOptions.forwardErrorToEnvironment = false;
- 331 :
- 332 :
this.repository = new YeomanRepository(this.options.yeomanRepository);
- 333 :
- 334 :
if (!this.options.experimental) {
- 335 :
process.argv.forEach(value => {
- 336 :
if (value === '--experimental') {
- 337 :
this.options.experimental = true;
- 338 :
debug('Set environment as experimental');
- 339 :
}
- 340 :
});
- 341 :
}
- 342 :
- 343 :
this.loadSharedOptions(this.options);
- 344 :
if (this.sharedOptions.skipLocalCache === undefined) {
- 345 :
this.sharedOptions.skipLocalCache = true;
- 346 :
}
- 347 :
- 348 :
// Store the generators by paths and uniqueBy feature.
- 349 :
this._generatorsForPath = {};
- 350 :
this._generators = {};
- 351 :
- 352 :
// Store the YeomanCompose by paths and uniqueBy feature.
- 353 :
this._composeStore = {};
- 354 :
}
- 355 :
- 356 :
/**
- 357 :
* Load options passed to the Generator that should be used by the Environment.
- 358 :
*
- 359 :
* @param {Object} options
- 360 :
*/
- 361 :
loadEnvironmentOptions(options) {
- 362 :
const environmentOptions = _.pick(options, [
- 363 :
'skipInstall',
- 364 :
'nodePackageManager'
- 365 :
]);
- 366 :
_.defaults(this.options, environmentOptions);
- 367 :
return environmentOptions;
- 368 :
}
- 369 :
- 370 :
/**
- 371 :
* Load options passed to the Environment that should be forwarded to the Generator.
- 372 :
*
- 373 :
* @param {Object} options
- 374 :
*/
- 375 :
loadSharedOptions(options) {
- 376 :
const optionsToShare = _.pick(options, [
- 377 :
'skipInstall',
- 378 :
'forceInstall',
- 379 :
'skipCache',
- 380 :
'skipLocalCache',
- 381 :
'skipParseOptions',
- 382 :
'localConfigOnly',
- 383 :
'askAnswered'
- 384 :
]);
- 385 :
Object.assign(this.sharedOptions, optionsToShare);
- 386 :
return optionsToShare;
- 387 :
}
- 388 :
- 389 :
/**
- 390 :
* @deprecated
- 391 :
* Error handler taking `err` instance of Error.
- 392 :
*
- 393 :
* The `error` event is emitted with the error object, if no `error` listener
- 394 :
* is registered, then we throw the error.
- 395 :
*
- 396 :
* @param {Object} err
- 397 :
* @return {Error} err
- 398 :
*/
- 399 :
error(error) {
- 400 :
throw error instanceof Error ? error : new Error(error);
- 401 :
}
- 402 :
- 403 :
/**
- 404 :
* Outputs the general help and usage. Optionally, if generators have been
- 405 :
* registered, the list of available generators is also displayed.
- 406 :
*
- 407 :
* @param {String} name
- 408 :
*/
- 409 :
help(name = 'init') {
- 410 :
const out = [
- 411 :
'Usage: :binary: GENERATOR [args] [options]',
- 412 :
'',
- 413 :
'General options:',
- 414 :
' --help # Print generator\'s options and usage',
- 415 :
' -f, --force # Overwrite files that already exist',
- 416 :
'',
- 417 :
'Please choose a generator below.',
- 418 :
''
- 419 :
];
- 420 :
- 421 :
const ns = this.namespaces();
- 422 :
- 423 :
const groups = {};
- 424 :
for (const namespace of ns) {
- 425 :
const base = namespace.split(':')[0];
- 426 :
- 427 :
if (!groups[base]) {
- 428 :
groups[base] = [];
- 429 :
}
- 430 :
- 431 :
groups[base].push(namespace);
- 432 :
}
- 433 :
- 434 :
for (const key of Object.keys(groups).sort()) {
- 435 :
const group = groups[key];
- 436 :
- 437 :
if (group.length > 0) {
- 438 :
out.push('', key.charAt(0).toUpperCase() + key.slice(1));
- 439 :
}
- 440 :
- 441 :
for (const ns of groups[key]) {
- 442 :
out.push(` ${ns}`);
- 443 :
}
- 444 :
}
- 445 :
- 446 :
return out.join('\n').replace(/:binary:/g, name);
- 447 :
}
- 448 :
- 449 :
/**
- 450 :
* Registers a specific `generator` to this environment. This generator is stored under
- 451 :
* provided namespace, or a default namespace format if none if available.
- 452 :
*
- 453 :
* @param {String} name - Filepath to the a generator or a npm package name
- 454 :
* @param {String} namespace - Namespace under which register the generator (optional)
- 455 :
* @param {String} packagePath - PackagePath to the generator npm package (optional)
- 456 :
* @return {Object} environment - This environment
- 457 :
*/
- 458 :
register(name, namespace, packagePath) {
- 459 :
if (typeof name !== 'string') {
- 460 :
throw new TypeError('You must provide a generator name to register.');
- 461 :
}
- 462 :
- 463 :
const modulePath = this.resolveModulePath(name);
- 464 :
namespace = namespace || this.namespace(modulePath);
- 465 :
- 466 :
if (!namespace) {
- 467 :
throw new Error('Unable to determine namespace.');
- 468 :
}
- 469 :
- 470 :
// Generator is already registered and matches the current namespace.
- 471 :
if (this.store._meta[namespace] && this.store._meta[namespace].resolved === modulePath) {
- 472 :
return this;
- 473 :
}
- 474 :
- 475 :
this.store.add(namespace, modulePath, modulePath, packagePath);
- 476 :
const packageNS = Environment.namespaceToName(namespace);
- 477 :
this.store.addPackageNS(packageNS);
- 478 :
if (packagePath) {
- 479 :
this.store.addPackage(packageNS, packagePath);
- 480 :
}
- 481 :
- 482 :
debug('Registered %s (%s) on package %s (%s)', namespace, modulePath, packageNS, packagePath);
- 483 :
return this;
- 484 :
}
- 485 :
- 486 :
/**
- 487 :
* Register a stubbed generator to this environment. This method allow to register raw
- 488 :
* functions under the provided namespace. `registerStub` will enforce the function passed
- 489 :
* to extend the Base generator automatically.
- 490 :
*
- 491 :
* @param {Function} Generator - A Generator constructor or a simple function
- 492 :
* @param {String} namespace - Namespace under which register the generator
- 493 :
* @param {String} [resolved] - The file path to the generator
- 494 :
* @param {String} [packagePath] - The generator's package path
- 495 :
* @return {this}
- 496 :
*/
- 497 :
registerStub(Generator, namespace, resolved = Environment.UNKNOWN_RESOLVED, packagePath = undefined) {
- 498 :
if (typeof Generator !== 'function' && typeof Generator.createGenerator !== 'function') {
- 499 :
throw new TypeError('You must provide a stub function to register.');
- 500 :
}
- 501 :
- 502 :
if (typeof namespace !== 'string') {
- 503 :
throw new TypeError('You must provide a namespace to register.');
- 504 :
}
- 505 :
- 506 :
this.store.add(namespace, Generator, resolved, packagePath);
- 507 :
const packageNS = Environment.namespaceToName(namespace);
- 508 :
this.store.addPackageNS(packageNS);
- 509 :
if (packagePath) {
- 510 :
this.store.addPackage(packageNS, packagePath);
- 511 :
}
- 512 :
- 513 :
debug('Registered %s (%s) on package (%s)', namespace, resolved, packagePath);
- 514 :
return this;
- 515 :
}
- 516 :
- 517 :
/**
- 518 :
* Returns the list of registered namespace.
- 519 :
* @return {Array}
- 520 :
*/
- 521 :
namespaces() {
- 522 :
return this.store.namespaces();
- 523 :
}
- 524 :
- 525 :
/**
- 526 :
* Returns the environment or dependency version.
- 527 :
* @param {String} packageName - Module to get version.
- 528 :
* @return {String} Environment version.
- 529 :
*/
- 530 :
getVersion(packageName) {
- 531 :
if (packageName && packageName !== 'yeoman-environment') {
- 532 :
try {
- 533 :
return require(`${packageName}/package.json`).version;
- 534 :
} catch {
- 535 :
return undefined;
- 536 :
}
- 537 :
}
- 538 :
return ENVIRONMENT_VERSION;
- 539 :
}
- 540 :
- 541 :
/**
- 542 :
* Returns stored generators meta
- 543 :
* @return {Object}
- 544 :
*/
- 545 :
getGeneratorsMeta() {
- 546 :
return this.store.getGeneratorsMeta();
- 547 :
}
- 548 :
- 549 :
/**
- 550 :
* Get registered generators names
- 551 :
*
- 552 :
* @return {Array}
- 553 :
*/
- 554 :
getGeneratorNames() {
- 555 :
return _.uniq(Object.keys(this.getGeneratorsMeta()).map(namespace => Environment.namespaceToName(namespace)));
- 556 :
}
- 557 :
- 558 :
/**
- 559 :
* Verify if a package namespace already have been registered.
- 560 :
*
- 561 :
* @param {String} [packageNS] - namespace of the package.
- 562 :
* @return {boolean} - true if any generator of the package has been registered
- 563 :
*/
- 564 :
isPackageRegistered(packageNS) {
- 565 :
return this.getRegisteredPackages().includes(packageNS);
- 566 :
}
- 567 :
- 568 :
/**
- 569 :
* Get all registered packages namespaces.
- 570 :
*
- 571 :
* @return {Array} - array of namespaces.
- 572 :
*/
- 573 :
getRegisteredPackages() {
- 574 :
return this.store.getPackagesNS();
- 575 :
}
- 576 :
- 577 :
/**
- 578 :
* Get last added path for a namespace
- 579 :
*
- 580 :
* @param {String} - namespace
- 581 :
* @return {String} - path of the package
- 582 :
*/
- 583 :
getPackagePath(namespace) {
- 584 :
if (namespace.includes(':')) {
- 585 :
const generator = this.get(namespace) || {};
- 586 :
return generator.packagePath;
- 587 :
}
- 588 :
const packagePaths = this.getPackagePaths(namespace) || [];
- 589 :
return packagePaths[0];
- 590 :
}
- 591 :
- 592 :
/**
- 593 :
* Get paths for a namespace
- 594 :
*
- 595 :
* @param {String} - namespace
- 596 :
* @return {Array} - array of paths.
- 597 :
*/
- 598 :
getPackagePaths(namespace) {
- 599 :
return this.store.getPackagesPaths()[namespace] ||
- 600 :
this.store.getPackagesPaths()[Environment.namespaceToName(this.alias(namespace))];
- 601 :
}
- 602 :
- 603 :
/**
- 604 :
* Get a single generator from the registered list of generators. The lookup is
- 605 :
* based on generator's namespace, "walking up" the namespaces until a matching
- 606 :
* is found. Eg. if an `angular:common` namespace is registered, and we try to
- 607 :
* get `angular:common:all` then we get `angular:common` as a fallback (unless
- 608 :
* an `angular:common:all` generator is registered).
- 609 :
*
- 610 :
* @param {String} namespaceOrPath
- 611 :
* @return {Generator|null} - the generator registered under the namespace
- 612 :
*/
- 613 :
get(namespaceOrPath) {
- 614 :
// Stop the recursive search if nothing is left
- 615 :
if (!namespaceOrPath) {
- 616 :
return;
- 617 :
}
- 618 :
- 619 :
const parsed = this.toNamespace ? this.toNamespace(namespaceOrPath) : undefined;
- 620 :
if (parsed && this.getByNamespace) {
- 621 :
return this.getByNamespace(parsed);
- 622 :
}
- 623 :
- 624 :
let namespace = namespaceOrPath;
- 625 :
- 626 :
// Legacy yeoman-generator `#hookFor()` function is passing the generator path as part
- 627 :
// of the namespace. If we find a path delimiter in the namespace, then ignore the
- 628 :
// last part of the namespace.
- 629 :
const parts = namespaceOrPath.split(':');
- 630 :
const maybePath = _.last(parts);
- 631 :
if (parts.length > 1 && /[/\\]/.test(maybePath)) {
- 632 :
parts.pop();
- 633 :
- 634 :
// We also want to remove the drive letter on windows
- 635 :
if (maybePath.includes('\\') && _.last(parts).length === 1) {
- 636 :
parts.pop();
- 637 :
}
- 638 :
- 639 :
namespace = parts.join(':');
- 640 :
}
- 641 :
- 642 :
const maybeGenerator = this.store.get(namespace) ||
- 643 :
this.store.get(this.alias(namespace)) ||
- 644 :
// Namespace is empty if namespaceOrPath contains a win32 absolute path of the form 'C:\path\to\generator'.
- 645 :
// for this reason we pass namespaceOrPath to the getByPath function.
- 646 :
this.getByPath(namespaceOrPath);
- 647 :
if (maybeGenerator && maybeGenerator.then) {
- 648 :
return maybeGenerator.then(Generator => this._findGeneratorClass(Generator));
- 649 :
}
- 650 :
return this._findGeneratorClass(maybeGenerator);
- 651 :
}
- 652 :
- 653 :
/**
- 654 :
* Get a generator by path instead of namespace.
- 655 :
* @param {String} path
- 656 :
* @return {Generator|null} - the generator found at the location
- 657 :
*/
- 658 :
getByPath(path) {
- 659 :
if (fs.existsSync(path)) {
- 660 :
const namespace = this.namespace(path);
- 661 :
this.register(path, namespace);
- 662 :
- 663 :
return this.get(namespace);
- 664 :
}
- 665 :
}
- 666 :
- 667 :
/**
- 668 :
* Find generator's class constructor.
- 669 :
* @private
- 670 :
* @param {Object} Generator - Object containing the class.
- 671 :
* @return {Function} Generator's constructor.
- 672 :
*/
- 673 :
_findGeneratorClass(Generator) {
- 674 :
if (!Generator) {
- 675 :
return Generator;
- 676 :
}
- 677 :
let meta = Generator;
- 678 :
if (Array.isArray(Generator)) {
- 679 :
meta = Generator[1];
- 680 :
Generator = Generator[0];
- 681 :
}
- 682 :
if (typeof Generator.default === 'function') {
- 683 :
Generator.default.resolved = meta.resolved;
- 684 :
Generator.default.namespace = meta.namespace;
- 685 :
return Generator.default;
- 686 :
}
- 687 :
if (typeof Generator.createGenerator === 'function') {
- 688 :
const maybeGenerator = Generator.createGenerator(this);
- 689 :
if (maybeGenerator.then) {
- 690 :
return maybeGenerator.then(Gen => {
- 691 :
Gen.resolved = meta.resolved;
- 692 :
Gen.namespace = meta.namespace;
- 693 :
return Gen;
- 694 :
});
- 695 :
}
- 696 :
maybeGenerator.resolved = meta.resolved;
- 697 :
maybeGenerator.namespace = meta.namespace;
- 698 :
return maybeGenerator;
- 699 :
}
- 700 :
if (typeof Generator !== 'function') {
- 701 :
throw new TypeError('The generator doesn\'t provides a constructor.');
- 702 :
}
- 703 :
return Generator;
- 704 :
}
- 705 :
- 706 :
/**
- 707 :
* Create is the Generator factory. It takes a namespace to lookup and optional
- 708 :
* hash of options, that lets you define `arguments` and `options` to
- 709 :
* instantiate the generator with.
- 710 :
*
- 711 :
* An error is raised on invalid namespace.
- 712 :
*
- 713 :
* @param {String} namespaceOrPath
- 714 :
* @param {Array} [args]
- 715 :
* @param {Object} [options]
- 716 :
* @return {Generator} The instantiated generator
- 717 :
*/
- 718 :
create(namespaceOrPath, args, options) {
- 719 :
if (!Array.isArray(args) && typeof args === 'object') {
- 720 :
options = args.options || args;
- 721 :
args = args.arguments || args.args || [];
- 722 :
} else {
- 723 :
args = Array.isArray(args) ? args : splitArgsFromString(args);
- 724 :
options = options || {};
- 725 :
}
- 726 :
- 727 :
const namespace = this.toNamespace ? this.toNamespace(namespaceOrPath) : undefined;
- 728 :
- 729 :
let maybeGenerator;
- 730 :
if (namespace && this.getByNamespace) {
- 731 :
maybeGenerator = this.getByNamespace(namespace);
- 732 :
if (!maybeGenerator) {
- 733 :
this.lookupLocalNamespaces(namespace);
- 734 :
maybeGenerator = this.getByNamespace(namespace);
- 735 :
}
- 736 :
}
- 737 :
- 738 :
const checkGenerator = Generator => {
- 739 :
if (namespace && Generator && Generator.namespace && Generator.namespace !== Environment.UNKNOWN_NAMESPACE) {
- 740 :
// Update namespace object in case of aliased namespace.
- 741 :
namespace.namespace = Generator.namespace;
- 742 :
}
- 743 :
- 744 :
if (typeof Generator !== 'function') {
- 745 :
const generatorHint = namespace ? namespace.generatorHint : getGeneratorHint(namespaceOrPath);
- 746 :
- 747 :
throw new Error(
- 748 :
chalk.red('You don\'t seem to have a generator with the name “' + namespaceOrPath + '” installed.') + '\n' +
- 749 :
'But help is on the way:\n\n' +
- 750 :
'You can see available generators via ' +
- 751 :
chalk.yellow('npm search yeoman-generator') + ' or via ' + chalk.yellow('http://yeoman.io/generators/') + '. \n' +
- 752 :
'Install them with ' + chalk.yellow(`npm install ${generatorHint}`) + '.\n\n' +
- 753 :
'To see all your installed generators run ' + chalk.yellow('yo') + ' without any arguments. ' +
- 754 :
'Adding the ' + chalk.yellow('--help') + ' option will also show subgenerators. \n\n' +
- 755 :
'If ' + chalk.yellow('yo') + ' cannot find the generator, run ' + chalk.yellow('yo doctor') + ' to troubleshoot your system.'
- 756 :
);
- 757 :
}
- 758 :
return Generator;
- 759 :
};
- 760 :
- 761 :
maybeGenerator = maybeGenerator || this.get(namespaceOrPath);
- 762 :
if (maybeGenerator && maybeGenerator.then) {
- 763 :
return maybeGenerator.then(Generator => checkGenerator(Generator)).then(Generator => this.instantiate(Generator, args, options));
- 764 :
}
- 765 :
- 766 :
return this.instantiate(checkGenerator(maybeGenerator), args, options);
- 767 :
}
- 768 :
- 769 :
/**
- 770 :
* Instantiate a Generator with metadatas
- 771 :
*
- 772 :
* @param {Class<Generator>} generator Generator class
- 773 :
* @param {Array} [args] Arguments to pass the instance
- 774 :
* @param {Object} [options] Options to pass the instance
- 775 :
* @return {Generator} The instantiated generator
- 776 :
*/
- 777 :
instantiate(Generator, args, options) {
- 778 :
if (!Array.isArray(args) && typeof args === 'object') {
- 779 :
options = args.options || args;
- 780 :
args = args.arguments || args.args || [];
- 781 :
} else {
- 782 :
args = Array.isArray(args) ? args : splitArgsFromString(args);
- 783 :
options = options || {};
- 784 :
}
- 785 :
- 786 :
const {namespace} = Generator;
- 787 :
- 788 :
const environmentOptions = {
- 789 :
env: this,
- 790 :
resolved: Generator.resolved || Environment.UNKNOWN_RESOLVED,
- 791 :
namespace
- 792 :
};
- 793 :
- 794 :
const generator = new Generator(args, {
- 795 :
...this.sharedOptions,
- 796 :
...options,
- 797 :
...environmentOptions
- 798 :
});
- 799 :
- 800 :
generator._environmentOptions = {
- 801 :
...this.options,
- 802 :
...this.sharedOptions,
- 803 :
...environmentOptions
- 804 :
};
- 805 :
- 806 :
return generator;
- 807 :
}
- 808 :
- 809 :
/**
- 810 :
* Compose with the generator.
- 811 :
*
- 812 :
* @param {String} namespaceOrPath
- 813 :
* @param {Array} [args]
- 814 :
* @param {Object} [options]
- 815 :
* @param {Boolean} [schedule]
- 816 :
* @return {Generator} The instantiated generator or the singleton instance.
- 817 :
*/
- 818 :
composeWith(generator, args, options, schedule = true) {
- 819 :
if (typeof args === 'boolean') {
- 820 :
schedule = args;
- 821 :
args = undefined;
- 822 :
options = undefined;
- 823 :
} else if (typeof options === 'boolean') {
- 824 :
schedule = options;
- 825 :
options = undefined;
- 826 :
}
- 827 :
const generatorInstance = this.create(generator, args, options);
- 828 :
if (generatorInstance.then) {
- 829 :
return generatorInstance.then(generatorInstance => this.queueGenerator(generatorInstance, schedule));
- 830 :
}
- 831 :
return this.queueGenerator(generatorInstance, schedule);
- 832 :
}
- 833 :
- 834 :
/**
- 835 :
* @private
- 836 :
*/
- 837 :
getGeneratorsForPath(generatorRoot = this.cwd) {
- 838 :
this._generatorsForPath[generatorRoot] = this._generatorsForPath[generatorRoot] || {};
- 839 :
return this._generatorsForPath[generatorRoot];
- 840 :
}
- 841 :
- 842 :
/**
- 843 :
* @private
- 844 :
*/
- 845 :
getGenerator(uniqueBy, generatorRoot = this.cwd) {
- 846 :
if (this._generators[uniqueBy]) {
- 847 :
return this._generators[uniqueBy];
- 848 :
}
- 849 :
return this.getGeneratorsForPath(generatorRoot)[uniqueBy];
- 850 :
}
- 851 :
- 852 :
/**
- 853 :
* @private
- 854 :
*/
- 855 :
getAllGenerators() {
- 856 :
return Object.fromEntries([
- 857 :
...Object.entries(this._generators),
- 858 :
...Object.entries(this._generatorsForPath).map(([root, generatorStore]) => {
- 859 :
return Object.entries(generatorStore).map(([namespace, generator]) => ([`${root}#${namespace}`, generator]));
- 860 :
}).flat()
- 861 :
]);
- 862 :
}
- 863 :
- 864 :
/**
- 865 :
* @private
- 866 :
*/
- 867 :
setGenerator(uniqueBy, generator) {
- 868 :
if (generator.features && generator.features.uniqueGlobally) {
- 869 :
this._generators[uniqueBy] = generator;
- 870 :
} else {
- 871 :
this.getGeneratorsForPath(generator.destinationRoot())[uniqueBy] = generator;
- 872 :
}
- 873 :
return generator;
- 874 :
}
- 875 :
- 876 :
/**
- 877 :
* Queue generator run (queue itself tasks).
- 878 :
*
- 879 :
* @param {Generator} generator Generator instance
- 880 :
* @param {boolean} [schedule=false] Whether to schedule the generator run.
- 881 :
* @return {Generator} The generator or singleton instance.
- 882 :
*/
- 883 :
queueGenerator(generator, schedule = false) {
- 884 :
const generatorFeatures = generator.getFeatures ? generator.getFeatures() : {};
- 885 :
let uniqueBy;
- 886 :
let rootUniqueBy;
- 887 :
let namespaceToEmit;
- 888 :
if (generatorFeatures) {
- 889 :
uniqueBy = generatorFeatures.uniqueBy;
- 890 :
namespaceToEmit = uniqueBy;
- 891 :
if (!generatorFeatures.uniqueGlobally) {
- 892 :
rootUniqueBy = generator.destinationRoot();
- 893 :
}
- 894 :
}
- 895 :
- 896 :
if (!uniqueBy) {
- 897 :
const {namespace} = generator.options;
- 898 :
const instanceId = crypto.randomBytes(20).toString('hex');
- 899 :
let namespaceDefinition = this.toNamespace(namespace);
- 900 :
if (namespaceDefinition) {
- 901 :
namespaceDefinition = namespaceDefinition.with({instanceId});
- 902 :
uniqueBy = namespaceDefinition.id;
- 903 :
namespaceToEmit = namespaceDefinition.namespace;
- 904 :
} else {
- 905 :
uniqueBy = `${namespace}#${instanceId}`;
- 906 :
namespaceToEmit = namespace;
- 907 :
}
- 908 :
}
- 909 :
- 910 :
const existing = this.getGenerator(uniqueBy, rootUniqueBy);
- 911 :
if (existing) {
- 912 :
debug(`Using existing generator for namespace ${uniqueBy}`);
- 913 :
return existing;
- 914 :
}
- 915 :
- 916 :
this.setGenerator(uniqueBy, generator);
- 917 :
this.emit('compose', namespaceToEmit, generator);
- 918 :
this.emit(`compose:${namespaceToEmit}`, generator);
- 919 :
- 920 :
const runGenerator = () => {
- 921 :
if (generator.queueTasks) {
- 922 :
// Generator > 5
- 923 :
this.once('run', () => generator.emit('run'));
- 924 :
this.once('end', () => generator.emit('end'));
- 925 :
return generator.queueTasks();
- 926 :
}
- 927 :
if (!generator.options.forwardErrorToEnvironment) {
- 928 :
generator.on('error', error => this.emit('error', error));
- 929 :
}
- 930 :
generator.promise = generator.run();
- 931 :
};
- 932 :
- 933 :
if (schedule) {
- 934 :
this.runLoop.add(
- 935 :
'environment:run',
- 936 :
async (done, stop) => {
- 937 :
try {
- 938 :
await runGenerator();
- 939 :
done();
- 940 :
} catch (error) {
- 941 :
stop(error);
- 942 :
}
- 943 :
}
- 944 :
);
- 945 :
} else {
- 946 :
const maybePromise = runGenerator();
- 947 :
if (maybePromise && maybePromise.then) {
- 948 :
return maybePromise.then(() => generator);
- 949 :
}
- 950 :
}
- 951 :
return generator;
- 952 :
}
- 953 :
- 954 :
/**
- 955 :
* Tries to locate and run a specific generator. The lookup is done depending
- 956 :
* on the provided arguments, options and the list of registered generators.
- 957 :
*
- 958 :
* When the environment was unable to resolve a generator, an error is raised.
- 959 :
*
- 960 :
* @param {String|Array} args
- 961 :
* @param {Object} [options]
- 962 :
*/
- 963 :
async run(args, options, done) {
- 964 :
if (done || typeof options === 'function' || typeof args === 'function') {
- 965 :
throw new Error('Callback support have been removed.');
- 966 :
}
- 967 :
- 968 :
args = Array.isArray(args) ? args : splitArgsFromString(args);
- 969 :
options = {...options};
- 970 :
- 971 :
const name = args.shift();
- 972 :
if (!name) {
- 973 :
throw new Error('Must provide at least one argument, the generator namespace to invoke.');
- 974 :
}
- 975 :
- 976 :
this.loadEnvironmentOptions(options);
- 977 :
- 978 :
const instantiateAndRun = async () => {
- 979 :
const generator = await this.create(name, args, {
- 980 :
...options,
- 981 :
initialGenerator: true
- 982 :
});
- 983 :
if (options.help) {
- 984 :
console.log(generator.help());
- 985 :
return undefined;
- 986 :
}
- 987 :
- 988 :
return this.runGenerator(generator);
- 989 :
};
- 990 :
- 991 :
if (this.options.experimental && !this.get(name)) {
- 992 :
debug(`Generator ${name} was not found, trying to install it`);
- 993 :
return this.prepareEnvironment(name).then(() => instantiateAndRun(), () => instantiateAndRun());
- 994 :
}
- 995 :
- 996 :
return instantiateAndRun();
- 997 :
}
- 998 :
- 999 :
/**
- 1000 :
* Start Environment queue
- 1001 :
* @param {Object} options - Conflicter options.
- 1002 :
*/
- 1003 :
start(options) {
- 1004 :
return new Promise((resolve, reject) => {
- 1005 :
if (this.conflicter === undefined) {
- 1006 :
const conflicterOptions = _.pick(
- 1007 :
_.defaults({}, this.options, options),
- 1008 :
['force', 'bail', 'ignoreWhitespace', 'dryRun', 'skipYoResolve', 'logCwd']
- 1009 :
);
- 1010 :
conflicterOptions.cwd = conflicterOptions.logCwd;
- 1011 :
- 1012 :
this.conflicter = new Conflicter(this.adapter, conflicterOptions);
- 1013 :
- 1014 :
this.queueConflicter();
- 1015 :
this.queuePackageManagerInstall();
- 1016 :
}
- 1017 :
- 1018 :
/*
- 1019 :
* Listen to errors and reject if emmited.
- 1020 :
* Some cases the generator relied at the behavior that the running process
- 1021 :
* would be killed if an error is thrown to environment.
- 1022 :
* Make sure to not rely on that behavior.
- 1023 :
*/
- 1024 :
this.on('error', error => {
- 1025 :
reject(error);
- 1026 :
});
- 1027 :
- 1028 :
/*
- 1029 :
* For backward compatibility
- 1030 :
*/
- 1031 :
this.on('generator:reject', error => {
- 1032 :
reject(error);
- 1033 :
});
- 1034 :
- 1035 :
this.on('generator:resolve', error => {
- 1036 :
resolve(error);
- 1037 :
});
- 1038 :
- 1039 :
this.runLoop.on('error', error => {
- 1040 :
this.emit('error', error);
- 1041 :
});
- 1042 :
- 1043 :
this.runLoop.on('paused', () => {
- 1044 :
this.emit('paused');
- 1045 :
});
- 1046 :
- 1047 :
this.once('end', () => {
- 1048 :
resolve();
- 1049 :
});
- 1050 :
- 1051 :
/* If runLoop has ended, the environment has ended too. */
- 1052 :
this.runLoop.once('end', () => {
- 1053 :
this.emit('end');
- 1054 :
});
- 1055 :
- 1056 :
this.emit('run');
- 1057 :
this.runLoop.start();
- 1058 :
});
- 1059 :
}
- 1060 :
- 1061 :
/**
- 1062 :
* Convenience method to run the generator with callbackWrapper.
- 1063 :
* See https://github.com/yeoman/environment/pull/101
- 1064 :
*
- 1065 :
* @param {Object} generator
- 1066 :
*/
- 1067 :
async runGenerator(generator) {
- 1068 :
try {
- 1069 :
generator = await generator;
- 1070 :
generator = await this.queueGenerator(generator);
- 1071 :
} catch (error) {
- 1072 :
return Promise.reject(error);
- 1073 :
}
- 1074 :
- 1075 :
this.compatibilityMode = generator.queueTasks ? false : 'v4';
- 1076 :
this._rootGenerator = this._rootGenerator || generator;
- 1077 :
- 1078 :
return this.start(generator.options);
- 1079 :
}
- 1080 :
- 1081 :
/**
- 1082 :
* Get the first generator that was queued to run in this environment.
- 1083 :
*
- 1084 :
* @return {Generator} generator queued to run in this environment.
- 1085 :
*/
- 1086 :
rootGenerator() {
- 1087 :
return this._rootGenerator;
- 1088 :
}
- 1089 :
- 1090 :
/**
- 1091 :
* Given a String `filepath`, tries to figure out the relative namespace.
- 1092 :
*
- 1093 :
* ### Examples:
- 1094 :
*
- 1095 :
* this.namespace('backbone/all/index.js');
- 1096 :
* // => backbone:all
- 1097 :
*
- 1098 :
* this.namespace('generator-backbone/model');
- 1099 :
* // => backbone:model
- 1100 :
*
- 1101 :
* this.namespace('backbone.js');
- 1102 :
* // => backbone
- 1103 :
*
- 1104 :
* this.namespace('generator-mocha/backbone/model/index.js');
- 1105 :
* // => mocha:backbone:model
- 1106 :
*
- 1107 :
* @param {String} filepath
- 1108 :
* @param {Array} lookups paths
- 1109 :
*/
- 1110 :
namespace(filepath, lookups = this.lookups) {
- 1111 :
if (!filepath) {
- 1112 :
throw new Error('Missing namespace');
- 1113 :
}
- 1114 :
- 1115 :
// Cleanup extension and normalize path for differents OS
- 1116 :
let ns = path.normalize(filepath.replace(new RegExp(escapeStrRe(path.extname(filepath)) + '$'), ''));
- 1117 :
- 1118 :
// Sort lookups by length so biggest are removed first
- 1119 :
const nsLookups = _(lookups.concat(['..'])).map(found => path.normalize(found)).sortBy('length').value().reverse();
- 1120 :
- 1121 :
// If `ns` contains a lookup dir in its path, remove it.
- 1122 :
ns = nsLookups.reduce((ns, lookup) => {
- 1123 :
// Only match full directory (begin with leading slash or start of input, end with trailing slash)
- 1124 :
lookup = new RegExp(`(?:\\\\|/|^)${escapeStrRe(lookup)}(?=\\\\|/)`, 'g');
- 1125 :
return ns.replace(lookup, '');
- 1126 :
}, ns);
- 1127 :
- 1128 :
const folders = ns.split(path.sep);
- 1129 :
const scope = _.findLast(folders, folder => folder.indexOf('@') === 0);
- 1130 :
- 1131 :
// Cleanup `ns` from unwanted parts and then normalize slashes to `:`
- 1132 :
ns = ns
- 1133 :
.replace(/(.*generator-)/, '') // Remove before `generator-`
- 1134 :
.replace(/[/\\](index|main)$/, '') // Remove `/index` or `/main`
- 1135 :
.replace(/^[/\\]+/, '') // Remove leading `/`
- 1136 :
.replace(/[/\\]+/g, ':'); // Replace slashes by `:`
- 1137 :
- 1138 :
if (scope) {
- 1139 :
ns = `${scope}/${ns}`;
- 1140 :
}
- 1141 :
- 1142 :
debug('Resolve namespaces for %s: %s', filepath, ns);
- 1143 :
- 1144 :
return ns;
- 1145 :
}
- 1146 :
- 1147 :
/**
- 1148 :
* Resolve a module path
- 1149 :
* @param {String} moduleId - Filepath or module name
- 1150 :
* @return {String} - The resolved path leading to the module
- 1151 :
*/
- 1152 :
resolveModulePath(moduleId) {
- 1153 :
if (moduleId[0] === '.') {
- 1154 :
moduleId = path.resolve(moduleId);
- 1155 :
}
- 1156 :
- 1157 :
moduleId = untildify(moduleId);
- 1158 :
moduleId = path.normalize(moduleId);
- 1159 :
- 1160 :
if (path.extname(moduleId) === '') {
- 1161 :
moduleId += path.sep;
- 1162 :
}
- 1163 :
- 1164 :
let resolved;
- 1165 :
// Win32: moduleId is resolving as moduleId.js or moduleId.json instead of moduleId/index.js, workaround it.
- 1166 :
if (process.platform === 'win32' && path.extname(moduleId) === '') {
- 1167 :
try {
- 1168 :
resolved = require.resolve(path.join(moduleId, 'index'));
- 1169 :
} catch {}
- 1170 :
}
- 1171 :
- 1172 :
return resolved || require.resolve(moduleId);
- 1173 :
}
- 1174 :
- 1175 :
/**
- 1176 :
* Apply transform streams to file in MemFs.
- 1177 :
* @param {Transform[]} transformStreams - transform streams to be applied.
- 1178 :
* @param {Stream} [stream] - files stream, defaults to this.sharedFs.stream().
- 1179 :
* @return {Promise}
- 1180 :
*/
- 1181 :
applyTransforms(transformStreams, options = {}) {
- 1182 :
const {
- 1183 :
stream = this.sharedFs.stream(),
- 1184 :
name = 'Tranforming'
- 1185 :
} = options;
- 1186 :
- 1187 :
let {log = true} = options;
- 1188 :
- 1189 :
if (log) {
- 1190 :
npmlog.tracker = new TrackerGroup();
- 1191 :
npmlog.enableProgress();
- 1192 :
log = npmlog.newItem(name);
- 1193 :
}
- 1194 :
- 1195 :
if (!Array.isArray(transformStreams)) {
- 1196 :
transformStreams = [transformStreams];
- 1197 :
}
- 1198 :
return pipeline(
- 1199 :
stream,
- 1200 :
createModifiedTransform(),
- 1201 :
...transformStreams,
- 1202 :
createEachFileTransform(file => {
- 1203 :
if (log) {
- 1204 :
log.completeWork(10);
- 1205 :
npmlog.info('Completed', path.relative(this.logCwd, file.path));
- 1206 :
}
- 1207 :
}, {autoForward: false, logName: 'environment:log'})
- 1208 :
).then(() => {
- 1209 :
if (log) {
- 1210 :
log.finish();
- 1211 :
npmlog.disableProgress();
- 1212 :
}
- 1213 :
});
- 1214 :
}
- 1215 :
- 1216 :
/**
- 1217 :
* Commits the MemFs to the disc.
- 1218 :
* @param {Stream} [stream] - files stream, defaults to this.sharedFs.stream().
- 1219 :
* @return {Promise}
- 1220 :
*/
- 1221 :
commitSharedFs(stream = this.sharedFs.stream()) {
- 1222 :
return new Promise((resolve, reject) => {
- 1223 :
debug('committing files');
- 1224 :
this.fs.commit([
- 1225 :
createYoResolveTransform(this.conflicter),
- 1226 :
createYoRcTransform(),
- 1227 :
createConflicterCheckTransform(this.conflicter),
- 1228 :
createConflicterStatusTransform(),
- 1229 :
// Use custom commit transform due to out of order transform.
- 1230 :
createCommitTransform(this.fs)
- 1231 :
],
- 1232 :
stream,
- 1233 :
(error, value) => {
- 1234 :
debug('committing finished');
- 1235 :
if (error) {
- 1236 :
reject(error);
- 1237 :
return;
- 1238 :
}
- 1239 :
- 1240 :
// Force to empty Conflicter queue.
- 1241 :
this.conflicter.queue.once('end', () => resolve(value));
- 1242 :
this.conflicter.queue.run();
- 1243 :
});
- 1244 :
});
- 1245 :
}
- 1246 :
- 1247 :
/**
- 1248 :
* Queue environment's commit task.
- 1249 :
*/
- 1250 :
queueConflicter() {
- 1251 :
const queueCommit = () => {
- 1252 :
debug('Queueing conflicts task');
- 1253 :
this.runLoop.add('environment:conflicts', (done, stop) => {
- 1254 :
let customCommitTask = this.findGeneratorCustomCommitTask();
- 1255 :
if (customCommitTask !== undefined && customCommitTask) {
- 1256 :
if (typeof customCommitTask !== 'function') {
- 1257 :
done();
- 1258 :
return;
- 1259 :
}
- 1260 :
} else {
- 1261 :
customCommitTask = this.commitSharedFs.bind(this);
- 1262 :
}
- 1263 :
const result = customCommitTask();
- 1264 :
if (!result || !result.then) {
- 1265 :
done();
- 1266 :
return;
- 1267 :
}
- 1268 :
return result.then(() => {
- 1269 :
debug('Adding queueCommit event listener');
- 1270 :
this.sharedFs.once('change', queueCommit);
- 1271 :
done();
- 1272 :
}
- 1273 :
, stop);
- 1274 :
}
- 1275 :
, {
- 1276 :
once: 'write memory fs to disk'
- 1277 :
});
- 1278 :
};
- 1279 :
- 1280 :
queueCommit();
- 1281 :
}
- 1282 :
- 1283 :
/**
- 1284 :
* Queue environment's package manager install task.
- 1285 :
*/
- 1286 :
queuePackageManagerInstall() {
- 1287 :
this.runLoop.add(
- 1288 :
'install',
- 1289 :
(done, stop) => this.packageManagerInstallTask().then(done, stop),
- 1290 :
{once: 'package manager install'}
- 1291 :
);
- 1292 :
}
- 1293 :
}
- 1294 :
- 1295 :
Object.assign(Environment.prototype, resolver);
- 1296 :
Object.assign(Environment.prototype, composability);
- 1297 :
Object.assign(Environment.prototype, require('./package-manager'));
- 1298 :
Object.assign(Environment.prototype, require('./spawn-command'));
- 1299 :
Object.assign(Environment.prototype, require('./namespace-composability'));
- 1300 :
- 1301 :
/**
- 1302 :
* Expose the utilities on the module
- 1303 :
* @see {@link env/util}
- 1304 :
*/
- 1305 :
Environment.util = require('./util/util');
- 1306 :
- 1307 :
module.exports = Environment;