From 097cd20c9686e2696f908e31ac98be2def9c4f42 Mon Sep 17 00:00:00 2001 From: Marcelo Boveto Shima Date: Sun, 14 Feb 2021 17:26:29 -0300 Subject: [PATCH] Implement package-json mixin. --- lib/actions/package-json.js | 53 +++++++++++++++++ lib/index.js | 24 +++++++- test/package-json.js | 110 ++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 lib/actions/package-json.js create mode 100644 test/package-json.js diff --git a/lib/actions/package-json.js b/lib/actions/package-json.js new file mode 100644 index 00000000..6497c461 --- /dev/null +++ b/lib/actions/package-json.js @@ -0,0 +1,53 @@ +'use strict'; + +/** + * @mixin + * @alias actions/packege-json + */ +module.exports = (cls) => + class extends cls { + /** + * @private + * Resolve the dependencies to be added to the package.json. + * + * @param {Object|string|string[]} dependencies + * @return {Promise} a 'packageName: packageVersion' object + */ + async _resolvePackageJsonDependencies(dependencies) { + if (typeof dependencies === 'string') { + dependencies = [dependencies]; + } else if (typeof dependencies !== 'object') { + throw new TypeError( + 'resolvePackageJsonDependencies requires an object' + ); + } else if (!Array.isArray(dependencies)) { + return dependencies; + } + + const entries = await Promise.all( + dependencies.map((dependency) => this.env.resolvePackage(dependency)) + ); + return Object.fromEntries(entries); + } + + /** + * @private + * Add dependencies to be added to the package.json. + * + * @param {Object|string|string[]} dependencies + * @return {Promise} a 'packageName: packageVersion' object + */ + async addDependencies(dependencies) { + dependencies = await this._resolvePackageJsonDependencies(dependencies); + this.packageJson.merge({dependencies}); + return dependencies; + } + + async addDevDependencies(devDependencies) { + devDependencies = await this._resolvePackageJsonDependencies( + devDependencies + ); + this.packageJson.merge({devDependencies}); + return devDependencies; + } + }; diff --git a/lib/index.js b/lib/index.js index 3013c405..2fce7936 100644 --- a/lib/index.js +++ b/lib/index.js @@ -20,6 +20,11 @@ const EMPTY = '@@_YEOMAN_EMPTY_MARKER_@@'; const debug = createDebug('yeoman:generator'); const ENV_VER_WITH_VER_API = '2.9.0'; +const mixins = [require('./actions/package-json')]; + +// eslint-disable-next-line unicorn/no-reduce +const Base = mixins.reduce((a, b) => b(a), EventEmitter); + // Ensure a prototype method is a candidate run by default const methodIsValid = function (name) { return !['_', '#'].includes(name.charAt(0)) && name !== 'constructor'; @@ -82,7 +87,7 @@ const runGenerator = (generator) => { * @typedef {Function} WrappedMethod */ -class Generator extends EventEmitter { +class Generator extends Base { // If for some reason environment adds more queues, we should use or own for stability. static get queues() { return [ @@ -965,6 +970,9 @@ class Generator extends EventEmitter { ); } + /** + * Generator config Storage. + */ get config() { if (!this._config) { this._config = this._getStorage(); @@ -973,6 +981,17 @@ class Generator extends EventEmitter { return this._config; } + /** + * Package.json Storage. + */ + get packageJson() { + if (!this._packageJson) { + this._packageJson = this.createStorage('package.json'); + } + + return this._packageJson; + } + /** * Ignore cancellable tasks. */ @@ -1228,6 +1247,8 @@ class Generator extends EventEmitter { // Reset the storage this._config = undefined; + // Reset packageJson + this._packageJson = undefined; } return this._destinationRoot || this.env.cwd; @@ -1329,6 +1350,7 @@ class Generator extends EventEmitter { _.extend(Generator.prototype, require('./actions/help')); _.extend(Generator.prototype, require('./actions/spawn-command')); _.extend(Generator.prototype, require('./actions/fs')); +_.extend(Generator.prototype, require('./actions/package-json')); Generator.prototype.user = require('./actions/user'); module.exports = Generator; diff --git a/test/package-json.js b/test/package-json.js new file mode 100644 index 00000000..f8835f79 --- /dev/null +++ b/test/package-json.js @@ -0,0 +1,110 @@ +'use strict'; +const assert = require('assert'); +const os = require('os'); +const path = require('path'); +const makeDir = require('make-dir'); +const rimraf = require('rimraf'); +const Environment = require('yeoman-environment'); + +const Base = require('..'); + +const tmpdir = path.join(os.tmpdir(), 'yeoman-package-json'); + +describe('Base#package-json', function () { + this.timeout(10000); + let generator; + let env; + + beforeEach(function () { + this.prevCwd = process.cwd(); + this.tmp = tmpdir; + makeDir.sync(path.join(tmpdir, 'subdir')); + process.chdir(tmpdir); + + env = Environment.createEnv(); + const Generator = class extends Base {}; + Generator.prototype.exec = function () {}; + generator = new Generator({ + env + }); + }); + + afterEach(function (done) { + process.chdir(this.prevCwd); + rimraf(tmpdir, done); + }); + + describe('_resolvePackageJsonDependencies()', () => { + it('should accept semver version', async () => { + assert.deepStrictEqual( + await generator._resolvePackageJsonDependencies('yeoman-generator@^2'), + {'yeoman-generator': '^2'} + ); + }); + + it('should accept github repository', async () => { + assert.deepStrictEqual( + await generator._resolvePackageJsonDependencies( + 'yeoman/generator#v4.13.0' + ), + {'yeoman-generator': 'github:yeoman/generator#v4.13.0'} + ); + }); + + it('should accept github repository version', async () => { + assert.deepStrictEqual( + await generator._resolvePackageJsonDependencies( + 'yeoman-generator@yeoman/generator#v4.13.0' + ), + {'yeoman-generator': 'github:yeoman/generator#v4.13.0'} + ); + }); + + it('should accept object and return it', async () => { + const a = {}; + assert.strictEqual(await generator._resolvePackageJsonDependencies(a), a); + }); + + it('should accept arrays', async () => { + assert.deepStrictEqual( + await generator._resolvePackageJsonDependencies([ + 'yeoman-generator@^2', + 'yeoman-environment@^2' + ]), + {'yeoman-generator': '^2', 'yeoman-environment': '^2'} + ); + }); + }); + + describe('addDependencies()', () => { + it('should generate dependencies inside package.json', async () => { + await generator.addDependencies('yeoman-generator@^2'); + assert.deepStrictEqual(generator.packageJson.getAll(), { + dependencies: {'yeoman-generator': '^2'} + }); + }); + + it('should accept object and merge inside package.json', async () => { + await generator.addDependencies({'yeoman-generator': '^2'}); + assert.deepStrictEqual(generator.packageJson.getAll(), { + dependencies: {'yeoman-generator': '^2'} + }); + }); + }); + + describe('addDependencies()', () => { + it('should generate dependencies inside package.json', async () => { + await generator.addDevDependencies('yeoman-generator@^2'); + assert.deepStrictEqual(generator.packageJson.getAll(), { + devDependencies: {'yeoman-generator': '^2'} + }); + }); + + it('should accept object and merge devDependencies inside package.json', async () => { + await generator.addDevDependencies({'yeoman-generator': '^2'}); + assert.deepStrictEqual(generator.packageJson.getAll(), { + devDependencies: {'yeoman-generator': '^2'} + }); + }); + }); +});