diff --git a/packages/cli/bin/install-edition.js b/packages/cli/bin/install-edition.js index a8cffe72b..3d6047fb6 100644 --- a/packages/cli/bin/install-edition.js +++ b/packages/cli/bin/install-edition.js @@ -11,6 +11,10 @@ const { writeJsonAsync, getJSONKey, } = require('./utils'); +const { + resolveFileInPackage, + resolveDirInPackage, +} = require('@pattern-lab/core/src/lib/resolver'); // https://github.com/TehShrike/deepmerge#overwrite-array const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray; @@ -31,7 +35,7 @@ const installEdition = (edition, config, projectDir) => { const sourceDir = config.paths.source.root; yield checkAndInstallPackage(edition); // 1 yield copyAsync( - path.resolve('./node_modules', edition, 'source', '_meta'), + resolveDirInPackage(edition, 'source', '_meta'), path.resolve(sourceDir, '_meta') ); // 2 pkg.dependencies = Object.assign( @@ -45,16 +49,15 @@ const installEdition = (edition, config, projectDir) => { // 4.1 case '@pattern-lab/edition-node-gulp': { yield copyAsync( - path.resolve('./node_modules', edition, 'gulpfile.js'), + resolveFileInPackage(edition, 'gulpfile.js'), path.resolve(sourceDir, '../', 'gulpfile.js') ); break; } // 4.2 case '@pattern-lab/edition-node': { - const editionPath = path.resolve('./node_modules', edition); - const editionConfigPath = path.resolve( - editionPath, + const editionConfigPath = resolveFileInPackage( + edition, 'patternlab-config.json' ); @@ -67,7 +70,7 @@ const installEdition = (edition, config, projectDir) => { ); yield copyAsync( - path.join(editionPath, path.sep, 'helpers', path.sep, 'test.js'), + resolveFileInPackage(edition, 'helpers', 'test.js'), path.resolve(sourceDir, '../', 'helpers/test.js') ); @@ -76,9 +79,8 @@ const installEdition = (edition, config, projectDir) => { } // 4.3 case '@pattern-lab/edition-twig': { - const editionPath = path.resolve('./node_modules', edition); - const editionConfigPath = path.resolve( - editionPath, + const editionConfigPath = resolveFileInPackage( + edition, 'patternlab-config.json' ); const editionConfig = require(editionConfigPath); @@ -90,7 +92,7 @@ const installEdition = (edition, config, projectDir) => { ); yield copyAsync( - path.resolve(editionPath, 'alter-twig.php'), + resolveFileInPackage(edition, 'alter-twig.php'), path.resolve(sourceDir, '../', 'alter-twig.php') ); diff --git a/packages/cli/bin/install-plugin.js b/packages/cli/bin/install-plugin.js index f297bd62a..ee1d18d5d 100644 --- a/packages/cli/bin/install-plugin.js +++ b/packages/cli/bin/install-plugin.js @@ -1,11 +1,9 @@ 'use strict'; -const path = require('path'); - const _ = require('lodash'); -const checkAndInstallPackage = require('./utils').checkAndInstallPackage; -const wrapAsync = require('./utils').wrapAsync; +const { checkAndInstallPackage, wrapAsync } = require('./utils'); +const { resolveFileInPackage } = require('@pattern-lab/core/src/lib/resolver'); const installPlugin = (plugin, config) => wrapAsync(function*() { @@ -16,9 +14,7 @@ const installPlugin = (plugin, config) => _.set(config, `plugins[${name}]['initialized']`, false); // Get the options from the plugin, if any - const pluginPathConfig = path.resolve( - path.join(process.cwd(), 'node_modules', name, 'config.json') - ); + const pluginPathConfig = resolveFileInPackage(name, 'config.json'); try { const pluginConfigJSON = require(pluginPathConfig); if (!_.has(config.plugins[name].options)) { diff --git a/packages/cli/bin/install-starterkit.js b/packages/cli/bin/install-starterkit.js index f7ce2b61c..0d3c115f0 100644 --- a/packages/cli/bin/install-starterkit.js +++ b/packages/cli/bin/install-starterkit.js @@ -7,16 +7,19 @@ const { checkAndInstallPackage, readJsonAsync, } = require('./utils'); +const { + resolveFileInPackage, + resolveDirInPackage, +} = require('@pattern-lab/core/src/lib/resolver'); const installStarterkit = (starterkit, config) => wrapAsync(function*() { const sourceDir = config.paths.source.root; const name = starterkit.value || starterkit; yield checkAndInstallPackage(name); - const kitPath = path.resolve('./node_modules', name); - yield copyAsync(path.resolve(kitPath, 'dist'), path.resolve(sourceDir)); + yield copyAsync(resolveDirInPackage(name, 'dist'), path.resolve(sourceDir)); let kitConfig; - const kitConfigPath = path.resolve(kitPath, 'patternlab-config.json'); + const kitConfigPath = resolveFileInPackage(name, 'patternlab-config.json'); if (fs.existsSync(kitConfigPath)) { kitConfig = yield readJsonAsync(kitConfigPath); } diff --git a/packages/cli/bin/utils.js b/packages/cli/bin/utils.js index 771a6f1a9..23ae9544a 100644 --- a/packages/cli/bin/utils.js +++ b/packages/cli/bin/utils.js @@ -6,6 +6,7 @@ const path = require('path'); const chalk = require('chalk'); const EventEmitter = require('events').EventEmitter; const hasYarn = require('has-yarn'); +const { resolveFileInPackage } = require('@pattern-lab/core/src/lib/resolver'); /** * @name log @@ -124,7 +125,7 @@ const copyWithPattern = (cwd, pattern, dest) => /** * @func fetchPackage - * @desc Fetches and saves packages from npm into node_modules and adds a reference in the package.json under dependencies + * @desc Fetches packages from an npm package registry and adds a reference in the package.json under dependencies * @param {string} packageName - The package name */ const fetchPackage = packageName => @@ -193,7 +194,7 @@ const getJSONKey = (packageName, key, fileName = 'package.json') => wrapAsync(function*() { yield checkAndInstallPackage(packageName); const jsonData = yield fs.readJson( - path.resolve('node_modules', packageName, fileName) + resolveFileInPackage(packageName, fileName) ); return jsonData[key]; }); diff --git a/packages/core/patternlab-config.json b/packages/core/patternlab-config.json index 519282d2c..7a9a6fa47 100644 --- a/packages/core/patternlab-config.json +++ b/packages/core/patternlab-config.json @@ -91,6 +91,7 @@ "uikits": [ { "name": "uikit-workshop", + "package": "@pattern-lab/uikit-workshop", "outputDir": "", "enabled": true, "excludedPatternStates": [], diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 1c575c3ab..7860142b1 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -205,7 +205,7 @@ const patternlab_module = function(config) { }, /** - * Installs plugin already available via `node_modules/` + * Installs plugin already available as a package dependency * * @memberof patternlab * @name installplugin @@ -214,8 +214,6 @@ const patternlab_module = function(config) { * @returns {void} */ installplugin: function(pluginName) { - //get the config - const configPath = path.resolve(process.cwd(), 'patternlab-config.json'); const plugin_manager = new pm(); plugin_manager.install_plugin(pluginName); @@ -234,7 +232,7 @@ const patternlab_module = function(config) { }, /** - * Loads starterkit already available via `node_modules/` + * Loads starterkit already available as a package dependency * * @memberof patternlab * @name loadstarterkit diff --git a/packages/core/src/lib/loaduikits.js b/packages/core/src/lib/loaduikits.js index dc152ae3d..19325f1ee 100644 --- a/packages/core/src/lib/loaduikits.js +++ b/packages/core/src/lib/loaduikits.js @@ -5,91 +5,110 @@ const _ = require('lodash'); const logger = require('./log'); -let findModules = require('./findModules'); //eslint-disable-line prefer-const -let fs = require('fs-extra'); // eslint-disable-line - -const uiKitMatcher = /^uikit-(.*)$/; -const nodeModulesPath = path.join(process.cwd(), 'node_modules'); - -/** - * Given a path: return the uikit name if the path points to a valid uikit - * module directory, or false if it doesn't. - * @param filePath - * @returns UIKit name if exists or FALSE - */ -const isUIKitModule = filePath => { - const baseName = path.basename(filePath); - const engineMatch = baseName.match(uiKitMatcher); +const { resolvePackageFolder } = require('./resolver'); - if (engineMatch) { - return engineMatch[1]; - } - return false; -}; +let fs = require('fs-extra'); // eslint-disable-line -const readModuleFile = (kit, subPath) => { +const readModuleFile = (uikitLocation, subPath) => { return fs.readFileSync( - path.resolve(path.join(kit.modulePath, subPath)), + path.resolve(path.join(uikitLocation, subPath)), 'utf8' ); }; /** * Loads uikits, connecting configuration and installed modules - * [1] Looks in node_modules for uikits. - * [2] Filter out our uikit-polyfills package. - * [3] Only continue if uikit is enabled in patternlab-config.json + * [1] Lists the enabled uikits from patternlab-config.json + * [2] Try to resolve the location of the uikit in the package dependencies + * [3] Warn when the uikit couldn't be loaded * [4] Reads files from uikit that apply to every template * @param {object} patternlab */ module.exports = patternlab => { const paths = patternlab.config.paths; - const uikits = findModules(nodeModulesPath, isUIKitModule) // [1] - .filter(kit => kit.name !== 'polyfills'); // [2] - uikits.forEach(kit => { - const configEntry = _.find(_.filter(patternlab.config.uikits, 'enabled'), { - name: `uikit-${kit.name}`, - }); // [3] - - if (!configEntry) { - logger.warning( - `Could not find uikit with name uikit-${kit.name} defined within patternlab-config.json, or it is not enabled.` - ); - return; + const uikitConfigs = _.filter(patternlab.config.uikits, 'enabled'); // [1] + uikitConfigs.forEach(uikitConfig => { + let uikitLocation = null; + if ('package' in uikitConfig) { + try { + uikitLocation = resolvePackageFolder(uikitConfig.package); + } catch (ex) { + logger.warning( + `Could not find uikit with package name ${uikitConfig.package}. Did you add it to the 'dependencies' section in your 'package.json' file?` + ); + return; + } + } else { + // For backwards compatibility, name to package calculation is: + // 1. name -> name + // 2. name -> uikit-name + // 3. name -> @pattern-lab/name + // 4. name -> @pattern-lab/uikit-name + for (const packageName of [ + uikitConfig.name, + `uikit-${uikitConfig.name}`, + `@pattern-lab/${uikitConfig.name}`, + `@pattern-lab/uikit-${uikitConfig.name}`, + ]) { + try { + uikitLocation = resolvePackageFolder(packageName); // [2] + } catch (ex) { + // Ignore + } + if (uikitLocation != null) { + uikitConfig.package = packageName; + logger.info(`Found uikit package ${packageName}`); + break; + } + } + if (uikitLocation == null) { + logger.warning( + `Could not find uikit with package name ${uikitConfig.name}, uikit-${uikitConfig.name}, @pattern-lab/${uikitConfig.name} or @pattern-lab/uikit-${uikitConfig.name} defined within patternlab-config.json in the package dependencies.` + ); + return; + } else { + logger.warning( + `Please update the configuration of UIKit ${uikitConfig.name} with property 'package: ${uikitConfig.package}' in patternlab-config.json. Lookup by 'name' is deprecated and will be removed in the future.` + ); + } // [3] } try { - patternlab.uikits[`uikit-${kit.name}`] = { - name: `uikit-${kit.name}`, - modulePath: kit.modulePath, + patternlab.uikits[uikitConfig.name] = { + name: uikitConfig.name, + package: uikitConfig.package, + modulePath: uikitLocation, enabled: true, - outputDir: configEntry.outputDir, - excludedPatternStates: configEntry.excludedPatternStates, - excludedTags: configEntry.excludedTags, + outputDir: uikitConfig.outputDir, + excludedPatternStates: uikitConfig.excludedPatternStates, + excludedTags: uikitConfig.excludedTags, header: readModuleFile( - kit, + uikitLocation, paths.source.patternlabFiles['general-header'] ), footer: readModuleFile( - kit, + uikitLocation, paths.source.patternlabFiles['general-footer'] ), patternSection: readModuleFile( - kit, + uikitLocation, paths.source.patternlabFiles.patternSection ), patternSectionSubType: readModuleFile( - kit, + uikitLocation, paths.source.patternlabFiles.patternSectionSubtype ), - viewAll: readModuleFile(kit, paths.source.patternlabFiles.viewall), + viewAll: readModuleFile( + uikitLocation, + paths.source.patternlabFiles.viewall + ), }; // [4] } catch (ex) { logger.error(ex); logger.error( '\nERROR: missing an essential file from ' + - kit.modulePath + + uikitLocation + paths.source.patternlabFiles + ". Pattern Lab won't work without this file.\n" ); diff --git a/packages/core/src/lib/resolver.js b/packages/core/src/lib/resolver.js new file mode 100644 index 000000000..9a988e8de --- /dev/null +++ b/packages/core/src/lib/resolver.js @@ -0,0 +1,33 @@ +'use strict'; + +const path = require('path'); + +/** + * @func resolveFileInPackage + * Resolves a file inside a package + */ +const resolveFileInPackage = (packageName, ...pathElements) => { + return require.resolve(path.join(packageName, ...pathElements)); +}; + +/** + * @func resolveDirInPackage + * Resolves a file inside a package + */ +const resolveDirInPackage = (packageName, ...pathElements) => { + return path.dirname(resolveFileInPackage(packageName, ...pathElements)); +}; + +/** + * @func resolvePackageFolder + * Resolves the location of a package on disc + */ +const resolvePackageFolder = packageName => { + return path.dirname(resolveFileInPackage(packageName, 'package.json')); +}; + +module.exports = { + resolveFileInPackage, + resolveDirInPackage, + resolvePackageFolder, +}; diff --git a/packages/core/src/lib/starterkit_manager.js b/packages/core/src/lib/starterkit_manager.js index d11e5f2a4..f355a8f55 100644 --- a/packages/core/src/lib/starterkit_manager.js +++ b/packages/core/src/lib/starterkit_manager.js @@ -29,7 +29,7 @@ const starterkit_manager = function(config) { kitDirStats = fs.statSync(kitPath); } catch (ex) { logger.warning( - `${starterkitName} not found, use npm to install it first.` + `${starterkitName} not found, use npm or another package manager to install it first.` ); logger.warning(`${starterkitName} not loaded.`); return; diff --git a/packages/core/test/loaduitkits_tests.js b/packages/core/test/loaduitkits_tests.js index 23bdcd656..b4581850d 100644 --- a/packages/core/test/loaduitkits_tests.js +++ b/packages/core/test/loaduitkits_tests.js @@ -8,41 +8,7 @@ const loaduikits = rewire('../src/lib/loaduikits'); const testConfig = require('./util/patternlab-config.json'); -const findModulesMock = function() { - return [ - { - name: 'foo', - modulePath: 'node_modules/@pattern-lab/uikit-foo', - }, - { - name: 'bar', - modulePath: 'node_modules/@pattern-lab/uikit-bar', - }, - { - name: 'baz', - modulePath: 'node_modules/@pattern-lab/uikit-baz', - }, - { - name: 'polyfills', - modulePath: 'node_modules/@pattern-lab/uikit-polyfills', - }, - ]; -}; - -const fsMock = { - readFileSync: function(path, encoding) { - return 'file'; - }, -}; - -loaduikits.__set__({ - findModules: findModulesMock, - fs: fsMock, -}); - -logger; - -tap.test('loaduitkits - does not warn on uikit-polyfills', test => { +tap.test('loaduikits - does warn on missing package property', test => { //arrange const patternlab = { config: testConfig, @@ -50,17 +16,7 @@ tap.test('loaduitkits - does not warn on uikit-polyfills', test => { }; patternlab.config.logLevel = 'warning'; - logger.log.on('warning', msg => test.notOk(msg.includes('uikit-polyfills'))); - - const uikitFoo = { - name: 'uikit-foo', - enabled: true, - outputDir: 'foo', - excludedPatternStates: ['legacy'], - excludedTags: ['baz'], - }; - - patternlab.config.uikits = [uikitFoo]; + logger.log.on('warning', msg => test.ok(msg.includes('package:'))); //act loaduikits(patternlab).then(() => { @@ -76,34 +32,24 @@ tap.test('loaduikits - maps fields correctly', function(test) { uikits: {}, }; - const uikitFoo = { - name: 'uikit-foo', - enabled: true, - outputDir: 'foo', - excludedPatternStates: ['legacy'], - excludedTags: ['baz'], - }; - - patternlab.config.uikits = [uikitFoo]; - //act loaduikits(patternlab).then(() => { //assert - test.equals(patternlab.uikits['uikit-foo'].name, uikitFoo.name); + test.equals(patternlab.uikits['uikit-workshop'].name, 'uikit-workshop'); test.equals( - patternlab.uikits['uikit-foo'].modulePath, - 'node_modules/@pattern-lab/uikit-foo' + patternlab.uikits['uikit-workshop'].package, + '@pattern-lab/uikit-workshop' ); - test.ok(patternlab.uikits['uikit-foo'].enabled); - test.equals(patternlab.uikits['uikit-foo'].outputDir, uikitFoo.outputDir); - test.equals( - patternlab.uikits['uikit-foo'].excludedPatternStates, - uikitFoo.excludedPatternStates - ); - test.equals( - patternlab.uikits['uikit-foo'].excludedTags, - uikitFoo.excludedTags + test.contains( + patternlab.uikits['uikit-workshop'].modulePath, + 'packages/uikit-workshop' ); + test.ok(patternlab.uikits['uikit-workshop'].enabled); + test.equals(patternlab.uikits['uikit-workshop'].outputDir, 'test/'); + test.deepEquals(patternlab.uikits['uikit-workshop'].excludedPatternStates, [ + 'legacy', + ]); + test.deepEquals(patternlab.uikits['uikit-workshop'].excludedTags, ['baz']); test.end(); }); }); @@ -115,36 +61,11 @@ tap.test('loaduikits - only adds files for enabled uikits', function(test) { uikits: {}, }; - patternlab.config.uikits = [ - { - name: 'uikit-foo', - enabled: true, - outputDir: 'foo', - excludedPatternStates: ['legacy'], - excludedTags: ['baz'], - }, - { - name: 'uikit-bar', - enabled: true, - outputDir: 'bar', - excludedPatternStates: ['development'], - excludedTags: ['baz', 'foo'], - }, - { - name: 'uikit-baz', - enabled: false, - outputDir: 'baz', - excludedPatternStates: [''], - excludedTags: [], - }, - ]; - //act loaduikits(patternlab).then(() => { //assert - test.ok(patternlab.uikits['uikit-foo']); - test.ok(patternlab.uikits['uikit-bar']); - test.notOk(patternlab.uikits['uikit-baz']); + test.ok(patternlab.uikits['uikit-workshop']); + test.notOk(patternlab.uikits['uikit-polyfills']); test.end(); }); }); diff --git a/packages/core/test/util/patternlab-config.json b/packages/core/test/util/patternlab-config.json index f4f1396c5..1d2e964f9 100644 --- a/packages/core/test/util/patternlab-config.json +++ b/packages/core/test/util/patternlab-config.json @@ -7,12 +7,12 @@ "meta": "./test/files/_meta/", "styleguide": "./test/files/styleguide/", "patternlabFiles": { - "general-header": "./test/files/partials/general-header.mustache", - "general-footer": "./test/files/partials/general-footer.mustache", - "patternSection": "./test/files/partials/patternSection.mustache", + "general-header": "views/partials/general-header.mustache", + "general-footer": "views/partials/general-footer.mustache", + "patternSection": "views/partials/patternSection.mustache", "patternSectionSubtype": - "./test/files/partials/patternSectionSubtype.mustache", - "viewall": "./test/files/viewall.mustache" + "views/partials/patternSectionSubtype.mustache", + "viewall": "views/viewall.mustache" }, "js": "./test/files/js", "images": "./test/files/images", @@ -76,8 +76,15 @@ "uikits": [ { "name": "uikit-workshop", - "outputDir": "packages/core/test/", + "outputDir": "test/", "enabled": true, + "excludedPatternStates": ["legacy"], + "excludedTags": ["baz"] + }, + { + "name": "uikit-polyfills", + "outputDir": "test/", + "enabled": false, "excludedPatternStates": [], "excludedTags": [] } diff --git a/packages/development-edition-engine-handlebars/patternlab-config.json b/packages/development-edition-engine-handlebars/patternlab-config.json index 0ba471dd2..36546fb07 100644 --- a/packages/development-edition-engine-handlebars/patternlab-config.json +++ b/packages/development-edition-engine-handlebars/patternlab-config.json @@ -86,6 +86,7 @@ "uikits": [ { "name": "uikit-workshop", + "package": "@pattern-lab/uikit-workshop", "outputDir": "", "enabled": true, "excludedPatternStates": [], diff --git a/packages/development-edition-engine-twig/patternlab-config.json b/packages/development-edition-engine-twig/patternlab-config.json index 77377bab0..43761ce19 100644 --- a/packages/development-edition-engine-twig/patternlab-config.json +++ b/packages/development-edition-engine-twig/patternlab-config.json @@ -95,6 +95,7 @@ "uikits": [ { "name": "uikit-workshop", + "package": "@pattern-lab/uikit-workshop", "outputDir": "", "enabled": true, "excludedPatternStates": [], diff --git a/packages/edition-node-gulp/patternlab-config.json b/packages/edition-node-gulp/patternlab-config.json index 02d705683..616402756 100644 --- a/packages/edition-node-gulp/patternlab-config.json +++ b/packages/edition-node-gulp/patternlab-config.json @@ -90,6 +90,7 @@ "uikits": [ { "name": "uikit-workshop", + "package": "@pattern-lab/uikit-workshop", "outputDir": "", "enabled": true, "excludedPatternStates": [], diff --git a/packages/edition-node/patternlab-config.json b/packages/edition-node/patternlab-config.json index 70fe58eca..d6df1cb46 100644 --- a/packages/edition-node/patternlab-config.json +++ b/packages/edition-node/patternlab-config.json @@ -90,6 +90,7 @@ "uikits": [ { "name": "uikit-workshop", + "package": "@pattern-lab/uikit-workshop", "outputDir": "", "enabled": true, "excludedPatternStates": [], diff --git a/packages/edition-twig/patternlab-config.json b/packages/edition-twig/patternlab-config.json index 3dddbfa29..58d633830 100644 --- a/packages/edition-twig/patternlab-config.json +++ b/packages/edition-twig/patternlab-config.json @@ -155,6 +155,7 @@ "uikits": [ { "name": "uikit-workshop", + "package": "@pattern-lab/uikit-workshop", "outputDir": "", "enabled": true, "excludedPatternStates": [],