diff --git a/README.md b/README.md index dbfb8d1..26b0bad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# @oracle/oraclejet-tooling 5.2.0 +# @oracle/oraclejet-tooling 6.0.0 ## About the tooling API This tooling API contains methods to build and serve Oracle JET web and hybrid mobile apps. It is intended to be used with task running tools such as grunt or gulp. The APIs can also be invoked directly. @@ -6,7 +6,7 @@ This tooling API contains methods to build and serve Oracle JET web and hybrid m This is an open source project maintained by Oracle Corp. ## Installation -This module will be automatically installed when you scaffold a web or hybrid mobile app following the [Oracle JET Developers Guide](http://www.oracle.com/pls/topic/lookup?ctx=jet520&id=homepage). +This module will be automatically installed when you scaffold a web or hybrid mobile app following the [Oracle JET Developers Guide](http://www.oracle.com/pls/topic/lookup?ctx=jet600&id=homepage). ## [Contributing](https://github.com/oracle/oraclejet-tooling/tree/master/CONTRIBUTING.md) Oracle JET is an open source project. Pull Requests are currently not being accepted. See diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9a7e4ed..d3f8d83 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,44 +1,46 @@ -## Release Notes for oraclejet-tooling ## - -### 5.2.0 -* No changes - -### 5.1.0 -* No changes - -### 5.0.0 -* No changes - -### 4.2.0 -* No changes - -### 4.1.0 -* No changes - -### 4.0.0 -* Moved module into @oracle scope, changing the name to @oracle/oraclejet-tooling - -### 3.2.0 -* No changes - -### 3.1.0 -* No changes - -### 3.0.0 -* Replaced bower with npm -* SASS tasks now run in CCA directories also -* Added --destination=server-only option for web apps -* Removed --destination=deviceOrEmulatorName option -* Added ability to cutomize serve tasks such as watching additional files -* Added gap://ready to inserted CSP meta tag for iOS 10 compatibility - -### 2.3.0 -* No changes - -### 2.2.0 -* Allow developers to configure release paths -* Provide help page for tooling tasks -* Allow multiple themes to be included in a built app -* Grunt serve to specific iOS emulator fails -* no-build option missing from grunt serve ->>>>>>> 0f5544d... Merge branch 'master' into ListPacks +## Release Notes for oraclejet-tooling ## + +### 6.0.0 +* No changes + +### 5.2.0 +* No changes + +### 5.1.0 +* No changes + +### 5.0.0 +* No changes + +### 4.2.0 +* No changes + +### 4.1.0 +* No changes + +### 4.0.0 +* Moved module into @oracle scope, changing the name to @oracle/oraclejet-tooling + +### 3.2.0 +* No changes + +### 3.1.0 +* No changes + +### 3.0.0 +* Replaced bower with npm +* SASS tasks now run in CCA directories also +* Added --destination=server-only option for web apps +* Removed --destination=deviceOrEmulatorName option +* Added ability to cutomize serve tasks such as watching additional files +* Added gap://ready to inserted CSP meta tag for iOS 10 compatibility + +### 2.3.0 +* No changes + +### 2.2.0 +* Allow developers to configure release paths +* Provide help page for tooling tasks +* Allow multiple themes to be included in a built app +* Grunt serve to specific iOS emulator fails +* no-build option missing from grunt serve diff --git a/lib/build.js b/lib/build.js index 4ae4cf9..f195971 100644 --- a/lib/build.js +++ b/lib/build.js @@ -6,6 +6,7 @@ const buildWeb = require('./buildWeb'); const buildHybrid = require('./buildHybrid'); +const buildComponent = require('./buildComponent'); const valid = require('./validations'); const config = require('./config'); /** @@ -31,6 +32,10 @@ const config = require('./config'); * @returns {Promise} */ module.exports = function build(platform, options) { + if (Object.prototype.hasOwnProperty.call(options, 'component')) { + config.loadOraclejetConfig('web'); + return buildComponent(options.component); + } config.loadOraclejetConfig(platform); const validPlatform = valid.platform(platform); const validOptions = valid.buildOptions(options, validPlatform); diff --git a/lib/buildCommon.js b/lib/buildCommon.js index 72245d4..014a3a4 100644 --- a/lib/buildCommon.js +++ b/lib/buildCommon.js @@ -453,4 +453,30 @@ module.exports = { return resolve(context); }); }, + copyReferenceCca(context) { + return new Promise((resolve) => { + util.log('Copy reference components to staging directory.'); + const componentList = util.getDirectories(`./${CONSTANTS.JET_COMPONENTS_DIRECTORY}`); + componentList.forEach((component) => { + const componentDirPath = `./${CONSTANTS.JET_COMPONENTS_DIRECTORY}/${component}/${CONSTANTS.JET_COMPONENT_JSON}`; + const componentJson = util.readJsonAndReturnObject(`${componentDirPath}`); + if (componentJson.type === 'reference') { + const npmPckgName = componentJson.package; + const npmPckgInitFileRelativePath = componentJson.paths.npm[context.buildType === 'release' ? 'min' : 'debug']; + + // Get only the file name + const npmPckgInitFileNameArray = npmPckgInitFileRelativePath.split('/'); + const npmPckgInitFileName = npmPckgInitFileNameArray[npmPckgInitFileNameArray.length - 1]; + + // Copy + const npmPckgSrcPath = `./${CONSTANTS.NODE_MODULES_DIRECTORY}/${npmPckgName}/${npmPckgInitFileRelativePath}`; + const destBasePath = path.join(config('paths').staging.stagingPath, config('paths').src.javascript, 'libs'); + const destNpmpckgDirPath = `${destBasePath}/${npmPckgName}/${npmPckgInitFileName}`; + fs.copySync(npmPckgSrcPath, destNpmpckgDirPath); + } + }); + util.log('Copy finished.'); + resolve(context); + }); + } }; diff --git a/lib/buildComponent.js b/lib/buildComponent.js new file mode 100644 index 0000000..09eb650 --- /dev/null +++ b/lib/buildComponent.js @@ -0,0 +1,84 @@ +/** + Copyright (c) 2015, 2018, Oracle and/or its affiliates. + The Universal Permissive License (UPL), Version 1.0 +*/ +'use strict'; + +const UglifyJS = require('uglify-es'); +const config = require('./config'); +const path = require('path'); +const util = require('./util'); +const fs = require('fs-extra'); +const glob = require('glob'); +const defaultOption = require('./defaultconfig'); + +function _uglifyComponent(componentPath) { + return new Promise((resolve, reject) => { + try { + const destPath = _getComponentDest(componentPath); + const files = glob.sync('**/*.js', { cwd: destPath }); + const uglifyOptions = defaultOption.build.uglify(config('paths')).options; + files.forEach((file) => { + const dest = path.join(destPath, file); + const data = _getUglyCode(dest, uglifyOptions); + if (data.error) reject(data.error); + fs.writeFileSync(dest, data.code); + }); + resolve(); + } catch (error) { + reject(error); + } + }); +} + +function _getComponentDest(componentPath) { + return path.join(componentPath, 'min'); +} + +function _getUglyCode(file, uglifyOptions) { + const code = fs.readFileSync(file, 'utf-8'); + return UglifyJS.minify(code, uglifyOptions); +} + +function _copyScriptsToDest(componentPath) { + const destPath = _getComponentDest(componentPath); + fs.removeSync(destPath); + // avoid recursively copy the min directory + const filter = function (src) { + return !/min/.test(src); + }; + + fs.copySync(componentPath, destPath, { filter }); +} + +function _getComponentPath(component) { + const basePath = path.join(config('paths').src.common, + config('paths').src.javascript, config('paths').composites); + const componentPath = path.join(basePath, component); + if (!util.fsExistsSync(componentPath)) { + util.log.error(`The component ${component} is not found`); + } + return componentPath; +} + +function _copyToStaging(component) { + const componentPath = _getComponentPath(component); + const destPath = path.join(config('paths').staging.web, + config('paths').src.javascript, config('paths').composites, component); + console.log(destPath); + fs.copySync(componentPath, destPath); +} + +module.exports = function buildComponent(component) { + return new Promise((resolve, reject) => { + const componentPath = _getComponentPath(component); + _copyScriptsToDest(componentPath); + _uglifyComponent(componentPath) + .then(() => { + _copyToStaging(component); + util.log(`Component ${component} build is finished.`); + resolve(); + }) + .catch(err => reject(err)); + }); +}; diff --git a/lib/buildHybrid.js b/lib/buildHybrid.js index f224017..fe12239 100644 --- a/lib/buildHybrid.js +++ b/lib/buildHybrid.js @@ -119,6 +119,7 @@ function _runCommonBuildTasks(context) { .then(buildCommon.clean) .then(buildCommon.copy) .then(buildCommon.copyLibs) + .then(buildCommon.copyReferenceCca) .then(buildCommon.copyLocalCca) .then(buildCommon.spriteSvg) .then(buildCommon.sass) diff --git a/lib/buildWeb.js b/lib/buildWeb.js index 2cac8f8..7249124 100644 --- a/lib/buildWeb.js +++ b/lib/buildWeb.js @@ -29,6 +29,7 @@ function _runCommonBuildTasks(context) { .then(data => hookRunner('before_build', data)) .then(buildCommon.copy) .then(buildCommon.copyLibs) + .then(buildCommon.copyReferenceCca) .then(buildCommon.copyLocalCca) .then(buildCommon.spriteSvg) .then(buildCommon.sass) diff --git a/lib/constants.js b/lib/constants.js index 1a0f999..ce53584 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -16,11 +16,12 @@ module.exports = { COMMON_THEME_DIRECTORY: 'common', JET_COMPOSITE_DIRECTORY: 'jet-composites', JET_COMPONENTS_DIRECTORY: 'jet_components', + NODE_MODULES_DIRECTORY: 'node_modules', JET_COMPONENT_JSON: 'component.json', SUPPORTED_PLATFORMS: ['android', 'ios', 'web', 'windows'], SUPPORTED_THEME_PLATFORMS: ['android', 'ios', 'web', 'windows', 'common'], SUPPORTED_HYBRID_PLATFORMS: ['android', 'ios', 'windows'], - SUPPORTED_BROWSERS: ['chrome', 'firefox', 'edge', 'ie', 'opera', 'safari'], + SUPPORTED_BROWSERS: ['chrome', 'firefox', 'edge', 'ie', 'safari'], SUPPORTED_WEB_PLATFORMS: ['web'], CORDOVA_CONFIG_XML: 'config.xml', @@ -47,6 +48,7 @@ module.exports = { API_TASKS: { ADD: 'add', CONFIGURE: 'configure', + CREATE: 'create', LIST: 'list', PUBLISH: 'publish', REMOVE: 'remove', diff --git a/lib/create.js b/lib/create.js new file mode 100644 index 0000000..381e9ca --- /dev/null +++ b/lib/create.js @@ -0,0 +1,32 @@ +#! /usr/bin/env node +/** + Copyright (c) 2015, 2018, Oracle and/or its affiliates. + The Universal Permissive License (UPL), Version 1.0 +*/ + +'use strict'; + +/** + * ## Dependencies + */ +const CONSTANTS = require('./constants'); +const pack = require('./scopes/pack'); +const util = require('./util'); + +/** + * # Switch for 'ojet.create()' + * + * @public + * @param {string} scope + * @param {string} parameter + * @returns {Promise} + */ +module.exports = function (scope, parameter) { + switch (scope) { + case (CONSTANTS.API_SCOPES.PACK): + return pack.create(parameter); + default: + util.log.error(`Please specify ojet.${CONSTANTS.API_TASKS.CREATE}() 'scope' parameter.`); + return false; + } +}; diff --git a/lib/rjsConfigGenerator.js b/lib/rjsConfigGenerator.js index 6aa6f11..35860c5 100644 --- a/lib/rjsConfigGenerator.js +++ b/lib/rjsConfigGenerator.js @@ -9,7 +9,6 @@ const path = require('path'); const util = require('./util'); const CONSTANTS = require('./constants'); const config = require('./config'); -const glob = require('glob'); function _getPathMappingObj(buildType, masterJson) { const obj = {}; @@ -18,6 +17,12 @@ function _getPathMappingObj(buildType, masterJson) { const libPath = _getLibPath(buildType, masterJson.libs[lib], useCdn, masterJson.cdns, lib); if (libPath) obj[lib] = libPath; }); + + // fix bug for require css broken link to css-builder.js + if (buildType === 'release') { + obj['css-builder'] = 'libs/require-css/css-builder'; + obj.normalize = 'libs/require-css/normalize'; + } return obj; } @@ -81,19 +86,43 @@ function _getRJsConfig(buildType, masterJson, oldConfig) { * ## _getCcaRJsConfig * @private * @param {String} buildType + * @param {Object} masterJson * @param {Object} config * @returns {Object} */ -function _getCcaRJsConfig(buildType, oldConfig) { +function _getCcaRJsConfig(buildType, masterJson, oldConfig) { // Update the requirejs optimizer config to skip bundling any minified cca components const newConfig = oldConfig; const dependenciesObj = util.readJsonAndReturnObject(`./${CONSTANTS.ORACLE_JET_CONFIG_JSON}`).dependencies; + + // Update build config with reference components + const componentList = util.getDirectories(`./${CONSTANTS.JET_COMPONENTS_DIRECTORY}`); + componentList.forEach((component) => { + const componentDirPath = `./${CONSTANTS.JET_COMPONENTS_DIRECTORY}/${component}/${CONSTANTS.JET_COMPONENT_JSON}`; + const componentJson = util.readJsonAndReturnObject(`${componentDirPath}`); + if (componentJson.type === 'reference') { + // Should cdn be used? && is paths.cdn property defined? + if (masterJson.use === 'cdn' && componentJson.cdn) { + // Is either release or debug url available? + if (componentJson.cdn.min || componentJson.cdn.debug) { + newConfig.paths[componentJson.paths.name || component] = 'empty:'; + } + } + } + }); + if (!dependenciesObj) return newConfig; Object.keys(dependenciesObj).forEach((dependency) => { const version = _isPack(dependenciesObj[dependency]) ? dependenciesObj[dependency].version : dependenciesObj[dependency]; if (buildType === 'release' && _isMinified(dependency, version)) newConfig.paths[dependency] = 'empty:'; }); + // bug fix for require-css broken link to css-build.js + if (config.exclude === undefined) { + newConfig.exclude = []; + } + newConfig.exclude.push('css-builder'); + newConfig.exclude.push('normalize'); return newConfig; } @@ -105,24 +134,49 @@ function _getCcaRJsConfig(buildType, oldConfig) { */ function _getCcaPathMapping(buildType) { const pathMappingObj = {}; - const dependenciesObj = util.readJsonAndReturnObject(`./${CONSTANTS.ORACLE_JET_CONFIG_JSON}`).dependencies; + const dependenciesObj = util.readJsonAndReturnObject(`./${CONSTANTS.ORACLE_JET_CONFIG_JSON}`).components; if (!dependenciesObj) return pathMappingObj; Object.keys(dependenciesObj).forEach((dependency) => { let dependencyPath = `${CONSTANTS.JET_COMPOSITE_DIRECTORY}/${dependency}`; - if (_isPack(dependenciesObj[dependency])) { - const version = dependenciesObj[dependency].version; - dependencyPath += `/${version}`; - if (buildType === 'release' && _isMinified(dependency, version)) dependencyPath += '/min'; + const dependencyComponentJsonPath = `./${CONSTANTS.JET_COMPONENTS_DIRECTORY}/${dependency}/${CONSTANTS.JET_COMPONENT_JSON}`; + const dependencyComponentJson = util.readJsonAndReturnObject(dependencyComponentJsonPath); + if (dependencyComponentJson.type === 'reference') { + const npmPackageName = `${dependencyComponentJson.package}`; + + // Use debug path by default + let npmPckgInitFileRelativePath = dependencyComponentJson.paths.npm.debug; + // For release, use minified version if available + if (buildType === 'release' && dependencyComponentJson.paths.npm.min) { + npmPckgInitFileRelativePath = dependencyComponentJson.paths.npm.min; + } + + // Get only the file name + const npmPckgInitFileNameArray = npmPckgInitFileRelativePath.split('/'); + let npmPckgInitFileName = npmPckgInitFileNameArray[npmPckgInitFileNameArray.length - 1]; + npmPckgInitFileName = npmPckgInitFileName.replace('.js', ''); + + // Set mapping + pathMappingObj[dependencyComponentJson.paths.name || dependency] = `libs/${npmPackageName}/${npmPckgInitFileName}`; } else { - const version = dependenciesObj[dependency]; - dependencyPath += `/${version}`; - if (buildType === 'release' && _isMinified(dependency, version)) dependencyPath += '/min'; + if (_isPack(dependenciesObj[dependency])) { + const version = _getValidVersion(dependenciesObj[dependency].version); + dependencyPath += `/${version}`; + if (buildType === 'release' && _isMinified(dependency, version)) dependencyPath += '/min'; + } else { + const version = _getValidVersion(dependenciesObj[dependency]); + dependencyPath += `/${version}`; + if (buildType === 'release' && _isMinified(dependency, version)) dependencyPath += '/min'; + } + pathMappingObj[dependency] = dependencyPath; } - pathMappingObj[dependency] = dependencyPath; }); return pathMappingObj; } +function _getValidVersion(version) { + return !isNaN(version.charAt(0)) ? version : version.substring(1); +} + /** * ## _getLocalCcaPathMapping * @private @@ -130,20 +184,43 @@ function _getCcaPathMapping(buildType) { */ function _getLocalCcaPathMapping() { const pathMappingObj = {}; - const basePath = path.join(config('paths').staging.stagingPath, config('paths').src.javascript, config('paths').composites); - const components = glob.sync('**/component.json', { cwd: basePath }); - components.forEach((componentPath) => { + const ccaVersionObj = {}; + const basePath = path.join(config('paths').src.common, config('paths').src.javascript, config('paths').composites); + const components = _getLocalComponentArray(); + components.forEach((componentDir) => { + const componentPath = path.join(componentDir, 'component.json'); const componentJson = util.readJsonAndReturnObject(path.join(basePath, componentPath)); + const version = Object.prototype.hasOwnProperty.call(componentJson, 'version') ? + componentJson.version : '1.0.0'; + ccaVersionObj[componentJson.name] = version; // if the component doesn't belong to a pack if (!Object.prototype.hasOwnProperty.call(componentJson, 'pack')) { - pathMappingObj[componentJson.name] = path.join(config('paths').composites, componentPath, '..'); + pathMappingObj[componentJson.name] = path.join(config('paths').composites, componentPath, '..', version); } else if (!Object.prototype.hasOwnProperty.call(pathMappingObj, componentJson.pack)) { - pathMappingObj[componentJson.pack] = path.join(config('paths').composites, componentPath, '..', '..'); + pathMappingObj[componentJson.pack] = path.join(config('paths').composites, componentPath, '..', '..', version); } }); + config('componentVersionObj', ccaVersionObj); return pathMappingObj; } +function _getLocalComponentArray() { + const basePath = path.join(config('paths').src.common, config('paths').src.javascript, config('paths').composites); + const localCca = []; + if (util.fsExistsSync(basePath)) { + const dirList = util.getDirectories(basePath); + dirList.forEach((dir) => { + const componentPath = path.join(basePath, dir, 'component.json'); + if (util.fsExistsSync(componentPath)) { + const componentObj = util.readJsonAndReturnObject(componentPath); + if (Object.prototype.hasOwnProperty.call(componentObj, 'name') && componentObj.name === dir) localCca.push(dir); + } + }); + } + + return localCca; +} + /** * ## _isPack * @private @@ -182,6 +259,6 @@ module.exports = { const rConfig = context.opts.requireJs; const buildType = context.buildType === 'release' ? 'release' : 'debug'; const rjsConfig = _getRJsConfig(buildType, masterJson, rConfig); - return _getCcaRJsConfig(buildType, rjsConfig); + return _getCcaRJsConfig(buildType, masterJson, rjsConfig); } }; diff --git a/lib/sass.js b/lib/sass.js index 6b66949..5e90542 100644 --- a/lib/sass.js +++ b/lib/sass.js @@ -8,6 +8,8 @@ const path = require('path'); const util = require('./util'); const fs = require('fs-extra'); const CONSTANT = require('./constants'); +const config = require('./config'); +const glob = require('glob'); function _writeSassResultToFile(options, result) { if (options.sourceMap) { @@ -37,7 +39,22 @@ function _getSassTaskPromise(options, context) { function _getSassDest(buildType, dest) { const name = path.basename(dest, '.scss'); const ext = util.getThemeCssExtention(buildType); - return util.destPath(path.join(path.dirname(dest), name + ext)); + let sassDest = util.destPath(path.join(path.dirname(dest), name + ext)); + if (util.isCcaSassFile(dest)) { + const base = path.join(config('paths').staging.stagingPath, config('paths').src.javascript, config('paths').composites); + const topDir = path.parse(path.relative(base, dest)).dir; + const components = glob.sync('**/component.json', { cwd: path.join(base, topDir) }); + // is standalone component + if (components.length === 1) { + sassDest = util.destPath(path.join(base, topDir, components[0], '..', name + ext)); + } else { + const componentJsonPath = path.join(base, topDir, 'component.json'); + const packVersion = util.fsExistsSync(componentJsonPath) ? + util.readJsonAndReturnObject(componentJsonPath).version : '1.0.0'; + sassDest = util.destPath(path.join(base, topDir, packVersion, name + ext)); + } + } + return sassDest; } function _getSassPromises(context) { diff --git a/lib/scopes/component.js b/lib/scopes/component.js index 33a71f7..4bdfb8a 100644 --- a/lib/scopes/component.js +++ b/lib/scopes/component.js @@ -11,6 +11,7 @@ */ // Node const fs = require('fs'); +const fse = require('fs-extra'); const http = require('http'); const https = require('https'); const path = require('path'); @@ -25,6 +26,7 @@ const FormData = require('form-data'); const CONSTANTS = require('../constants'); const exchangeUtils = require('../utils.exchange'); const util = require('../util'); +const build = require('../build'); /** * ## Variables @@ -53,11 +55,11 @@ component.add = function (componentNames, options) { exchangeUtils.resolve('add', componentNames, options) .then(_executeSolutions) .then(() => { - util.log.success(`Component(s) '${componentNames}' added.`, options); + util.log.success(`Component(s) '${componentNames}' added.`); resolve(); }) .catch((error) => { - util.log.error(error); + util.log.error(error, true); }); }); }; @@ -289,6 +291,7 @@ function _installComponents(componentNames) { _fetchMetadata(componentName) .then(_fetchArchive) .then(_unpackArchive) + .then(_installReferenceComponent) .then(() => { i += 1; fn(); @@ -413,100 +416,31 @@ function _unpackArchive(componentMetadata) { } /** - * ## list - * Lists installed components + * ## _installReferenceComponent * * @private + * @param {Object} componentMetadata + * @returns {Promise} */ -component.list = function () { - return new Promise((resolve) => { - // Read components from the config file - const componentsInConfigFile = []; - const configObj = util.readJsonAndReturnObject(`./${CONSTANTS.ORACLE_JET_CONFIG_JSON}`); - if (!util.isObjectEmpty(configObj.composites)) { - Object.keys(configObj.composites).forEach((key) => { - componentsInConfigFile.push(key); - }); - } - - // Read components by directories - let componentsByFolder = []; - if (fs.existsSync(componentsDirPath)) { - componentsByFolder = util.getDirectories(componentsDirPath); - } - - if (componentsByFolder.length === 0 && componentsInConfigFile.length === 0) { - util.log.success('No components found.'); - } - - // Output variables - const nameMaxLength = 30; - const space = ' '; - - // Print headline - const headlineName = 'name'; - const headlineNote = 'note'; - let headline = ''; - const headlineNameSpaces = nameMaxLength - headlineName.length; - if (headlineNameSpaces < 0) { - headline += `<${headlineName.substring(0, nameMaxLength - 2)}>`; +function _installReferenceComponent(componentMetadata) { + return new Promise((resolve, reject) => { + if (componentMetadata.type === 'reference') { + // Call npm install + const npmPackageName = componentMetadata.component.package; + util.log(`Installing npm package '${npmPackageName}' referenced by '${componentMetadata.name}.'`); + util.spawn('npm', ['install', npmPackageName]) + .then(() => { + util.log(`Npm package '${npmPackageName}' was successfully installed.`); + resolve(componentMetadata); + }) + .catch((error) => { + reject(error); + }); } else { - headline += `<${headlineName}>${space.repeat(headlineNameSpaces - 2)}`; + // Continue doing nothing + resolve(componentMetadata); } - headline += `${space}<${headlineNote}>`; - util.log(headline); - - // Print components list - componentsByFolder.forEach((comp) => { - let line = _constructLineOutput(comp, nameMaxLength, space); - line += `${space}${_addWarningMissingInConfig(comp, componentsInConfigFile)}`; - util.log(line); - }); - - // Print components from the config file which are not install - componentsInConfigFile.forEach((comp) => { - if (componentsByFolder.indexOf(comp) === -1) { - let line = _constructLineOutput(comp, nameMaxLength, space); - line += `${space}Warning: found in the config file but not installed. Please restore.`; - util.log(line); - } - }); - - util.log.success('Components listed.'); - resolve(); }); -}; - -/** - * ## _constructLineOutput - * - * @private - * @param {string} componentName - * @param {number} nameMaxLength - * @param {string} space - * @returns {string} - */ -function _constructLineOutput(componentName, nameMaxLength, space) { - const componentNameSpaces = nameMaxLength - componentName.length; - if (componentNameSpaces < 0) { - return `${componentName.substring(0, nameMaxLength)}`; - } - return `${componentName}${space.repeat(componentNameSpaces)}`; -} - -/** - * ## _addWarningMissingInConfig - * - * @private - * @param {string} componentName - * @param {Array} componentsInConfigFile - * @returns {string} - */ -function _addWarningMissingInConfig(componentName, componentsInConfigFile) { - if (componentsInConfigFile.indexOf(componentName) === -1) { - return 'Local component or installed as dependency. Not found in the config file.'; - } - return ''; } /** @@ -585,13 +519,21 @@ component.publish = function (componentName, options) { let packComponentJsonPath = ''; let packVersion = ''; + let buildPromise = new Promise((res) => { + res(); + }); + + if (options.release) { + buildPromise = build('', { component: componentName }); + } + if (!opts.pack) { // Component componentComponentJsonPath = path.join(CONSTANTS.APP_SRC_DIRECTORY, 'js', CONSTANTS.JET_COMPOSITE_DIRECTORY, componentName, CONSTANTS.JET_COMPONENT_JSON); if (fs.existsSync(componentComponentJsonPath)) { componentVersion = util.readJsonAndReturnObject(componentComponentJsonPath).version; } else { - util.log.error(`Component's main file '${componentComponentJsonPath}' does not exist.`); + util.log.error(`Component's descriptor '${componentComponentJsonPath}' does not exist.`); } } else { // Pack component @@ -603,10 +545,10 @@ component.publish = function (componentName, options) { if (fs.existsSync(componentComponentJsonPath)) { componentVersion = util.readJsonAndReturnObject(componentComponentJsonPath).version; } else { - util.log.error(`Pack component's main file '${componentComponentJsonPath}' does not exist.`); + util.log.error(`Pack component's descriptor '${componentComponentJsonPath}' does not exist.`); } } else { - util.log.error(`Pack's main file '${packComponentJsonPath}' does not exist.`); + util.log.error(`Pack's descriptor '${packComponentJsonPath}' does not exist.`); } } @@ -622,9 +564,17 @@ component.publish = function (componentName, options) { path.join(componentName, componentVersion); componentHybridDirPath = path.join(CONSTANTS.CORDOVA_DIRECTORY, 'www/js', CONSTANTS.JET_COMPOSITE_DIRECTORY, hybridEndDirPath); - const existsInWebDir = fs.existsSync(componentWebDirPath); + let existsInWebDir = fs.existsSync(componentWebDirPath); const existsInHybridDir = fs.existsSync(componentHybridDirPath); + if (!existsInWebDir && !existsInHybridDir) { + const componentDirSrcPath = opts.pack ? path.join(packComponentJsonPath, '..') + : path.join(componentComponentJsonPath, '..'); + const componentDirDestPath = path.join(CONSTANTS.WEB_DIRECTORY, 'js', CONSTANTS.JET_COMPOSITE_DIRECTORY, webEndDirPath); + fse.copySync(componentDirSrcPath, componentDirDestPath); + existsInWebDir = true; + } + if (existsInWebDir || existsInHybridDir) { let user = ''; let pass = ''; @@ -634,19 +584,25 @@ component.publish = function (componentName, options) { pass = opts.password; } - let initialPromise; + let loginUser; if (opts._accessToken) { - initialPromise = new Promise((res) => { - res(opts._accessToken); - }); + loginUser = function () { + return new Promise((res) => { + res(opts._accessToken); + }); + }; } else { - initialPromise = (user && typeof user !== 'boolean' && pass && typeof pass !== 'boolean') ? - exchangeUtils.getAccessToken(user, pass) : exchangeUtils.login(); + loginUser = function () { + return (user && typeof user !== 'boolean' && pass && typeof pass !== 'boolean') ? + exchangeUtils.getAccessToken(user, pass) : exchangeUtils.login(); + }; } const componentPath = existsInHybridDir ? componentHybridDirPath : componentWebDirPath; - initialPromise + + buildPromise + .then(loginUser) .then((resolvedAccessToken) => { opts._accessToken = resolvedAccessToken; return _packArchive(componentName, componentPath, opts); @@ -660,7 +616,7 @@ component.publish = function (componentName, options) { resolve(); }) .catch((error) => { - util.log.error(error); + util.log.error(error, true); }); } else { util.log.error(`Component '${componentName}' not found in built directories: @@ -796,7 +752,7 @@ component.remove = function (componentNames, isStrip, options) { resolve(); }) .catch((error) => { - util.log.error(error); + util.log.error(error, true); }); }); }; diff --git a/lib/scopes/exchange.js b/lib/scopes/exchange.js index eb90ab3..ddb7bd1 100644 --- a/lib/scopes/exchange.js +++ b/lib/scopes/exchange.js @@ -38,7 +38,7 @@ exchange.configureUrl = function (url) { util.log.success(`Exchange url set: '${url}'`); resolve(); } catch (e) { - util.log.error('Exchange url could not be set.'); + util.log.error('Exchange url could not be set.', true); } }); }; @@ -48,8 +48,10 @@ exchange.configureUrl = function (url) { * * @public * @param {string} parameter + * @param {Object} options + * @returns {Promise} */ -exchange.search = function (parameter) { +exchange.search = function (parameter, options) { return new Promise((resolve) => { util.ensureParameters(parameter, CONSTANTS.API_TASKS.SEARCH); util.log(`Searching for '${parameter}' in the Exchange ...`); @@ -67,13 +69,14 @@ exchange.search = function (parameter) { if (components.length === 0) { util.log.success('No components found.'); } else { + _customisePrintOutput(options); _printHead(); _printResults(components, parameter); } resolve(); - }).catch((error) => { - util.log.error(error); }); + }).catch((error) => { + util.log.error(error, true); }); }); }; @@ -87,6 +90,25 @@ const table = { const space = ' '; +/** + * ## _customisePrintOutput + * + * @private + * @param {Object} options + */ + +function _customisePrintOutput(options) { + // Versions + if (options.versions) { + // Do not show tags and description + delete table.tags; + delete table.description; + + // Use this space to show all available versions + table.versions = 80; + } +} + /** * ## _printHead * diff --git a/lib/scopes/pack.js b/lib/scopes/pack.js index 205c72f..e4e743a 100644 --- a/lib/scopes/pack.js +++ b/lib/scopes/pack.js @@ -50,6 +50,10 @@ pack.add = function (packNames) { // Add versions const packComponentNamesWithVersions = []; + // Add pack with version + packComponentNamesWithVersions.push(`${packName}@${metadata.version}`); + + // Ad components with versions Object.keys(packComponentNames).forEach((packComponentName) => { packComponentNamesWithVersions.push(`${packComponentName}@${packComponentNames[packComponentName]}`); }); @@ -69,7 +73,7 @@ pack.add = function (packNames) { fn(); }) .catch((error) => { - util.log.error(error); + util.log.error(error, true); reject(); }); } else { @@ -81,6 +85,42 @@ pack.add = function (packNames) { }); }; +/** + * ## create + * + * @public + * @param {string} packName + * @return {Promise} + */ +pack.create = function (packName) { + return new Promise((resolve) => { + util.ensureParameters(packName, CONSTANTS.API_TASKS.PUBLISH); + + const jetCompositesDirPath = path.join(process.cwd(), CONSTANTS.APP_SRC_DIRECTORY, 'js', CONSTANTS.JET_COMPOSITE_DIRECTORY); + const packDirPath = path.join(jetCompositesDirPath, packName); + // Check if already exists + if (fs.existsSync(packDirPath)) { + util.log.error(`Pack '${packName}' already exits.`); + } else { + // Make pack directory + util.ensureDir(jetCompositesDirPath); + util.ensureDir(packDirPath); + + // Add pack's component.json + const packComponentJsonTemplatePath = path.join(__dirname, '../templates/pack', CONSTANTS.JET_COMPONENT_JSON); + const packComponentJson = util.readJsonAndReturnObject(packComponentJsonTemplatePath); + packComponentJson.name = packName; + packComponentJson.jetVersion = `^${util.getJETVersion()}`; + const filename = path.join(packDirPath, CONSTANTS.JET_COMPONENT_JSON); + fs.writeFileSync(filename, JSON.stringify(packComponentJson, null, 2)); + util.log.success(`Pack '${packName}' successfully created.'`); + resolve(); + } + }).catch((error) => { + util.log.error(error, true); + }); +}; + /** * ## list * Lists installed packs @@ -123,6 +163,8 @@ pack.list = function () { util.printList(packsInConfigFile, packsByFolder); util.log.success('Packs listed.'); resolve(); + }).catch((error) => { + util.log.error(error, true); }); }; @@ -144,7 +186,7 @@ pack.publish = function (packName, options) { packComponentJson = util.readJsonAndReturnObject(packComponentJsonPath); packVersion = packComponentJson.version; } else { - util.log.error(`Pack's main file '${packComponentJsonPath}' does not exist.`); + util.log.error(`Pack's descriptor '${packComponentJsonPath}' does not exist.`); } const packWebDirPath = path.join(CONSTANTS.WEB_DIRECTORY, 'js', CONSTANTS.JET_COMPOSITE_DIRECTORY, packName, packVersion); @@ -173,7 +215,7 @@ pack.publish = function (packName, options) { util.readJsonAndReturnObject(packComponentComponentJsonPath).version }); } else { - util.log(`Pack component's main file ${packComponentComponentJsonPath}' not found. This component won't be published. Skipping.`); + util.log(`Pack component's descriptor ${packComponentComponentJsonPath}' not found. This component won't be published. Skipping.`); } }); @@ -238,7 +280,7 @@ pack.publish = function (packName, options) { resolve(); }) .catch((error) => { - util.log.error(error); + util.log.error(error, true); }); } else { util.log.error(`Pack '${packName}' not found in built directories: diff --git a/lib/search.js b/lib/search.js index 2448631..56b5b33 100644 --- a/lib/search.js +++ b/lib/search.js @@ -19,12 +19,13 @@ const util = require('./util'); * @public * @param {string} scope * @param {string} parameter + * @param {Object} options * @returns {Promise} */ -module.exports = function (scope, parameter) { +module.exports = function (scope, parameter, options) { switch (scope) { case (CONSTANTS.API_SCOPES.EXCHANGE): - return exchange.search(parameter); + return exchange.search(parameter, options); default: util.log.error(`Please specify ojet.${CONSTANTS.API_TASKS.SEARCH}() 'scope' parameter.`); return false; diff --git a/lib/serveHybridFileChangeHandler.js b/lib/serveHybridFileChangeHandler.js index 017e762..0d67afa 100644 --- a/lib/serveHybridFileChangeHandler.js +++ b/lib/serveHybridFileChangeHandler.js @@ -45,6 +45,17 @@ module.exports = (filePath, buildContext, isSrc) => { function _copyFileOver(filePath, buildContext) { /* Copies file over for the watch events */ const pathComponents = util.getPathComponents(filePath); + if (util.isPathCCA(pathComponents.end)) { + const name = util.getCCANameFromPath(pathComponents.end); + const version = config('componentVersionObj')[name]; + const basePathArray = pathComponents.end.split(path.sep); + let basePath = ''; + for (let i = 4; i < basePathArray.length; i++) { + basePath = path.join(basePath, basePathArray[i]); + } + const ccaEndPath = path.join(config('paths').src.javascript, config('paths').composites, name, version, basePath); + pathComponents.end = `/${ccaEndPath}`; + } if (_isFileOverridden(pathComponents)) { console.log(`Overridden file not changed: ${filePath}`); diff --git a/lib/serveWebFileChangeHandler.js b/lib/serveWebFileChangeHandler.js index 5de3479..786bbff 100644 --- a/lib/serveWebFileChangeHandler.js +++ b/lib/serveWebFileChangeHandler.js @@ -14,6 +14,7 @@ const fs = require('fs-extra'); const buildCommon = require('./buildCommon'); const config = require('./config'); const util = require('./util'); +const path = require('path'); /** * # serveWeb file change procedure @@ -29,7 +30,20 @@ module.exports = (filePath, buildContext) => { return; } /* Copies file over for the watch events */ - fs.copySync(filePath, pathComponents.beg + config('paths').staging.web + pathComponents.end); + if (util.isPathCCA(pathComponents.end)) { + const name = util.getCCANameFromPath(pathComponents.end); + const version = config('componentVersionObj')[name]; + const basePathArray = pathComponents.end.split(path.sep); + let basePath = ''; + for (let i = 4; i < basePathArray.length; i++) { + basePath = path.join(basePath, basePathArray[i]); + } + const dest = path.join(pathComponents.beg, config('paths').staging.web, + config('paths').src.javascript, config('paths').composites, name, version, basePath); + fs.copySync(filePath, dest); + } else { + fs.copySync(filePath, pathComponents.beg + config('paths').staging.web + pathComponents.end); + } _injectThemeIfIndexHtml(filePath, buildContext); }; @@ -78,7 +92,7 @@ function _isFileOverridden(pathComponents) { if (srcDir === config('paths').src.web) { return false; } - const path = pathComponents.beg + config('paths').src.web + pathComponents.end; + const filePath = pathComponents.beg + config('paths').src.web + pathComponents.end; - return util.fsExistsSync(path); + return util.fsExistsSync(filePath); } diff --git a/lib/templates/pack/component.json b/lib/templates/pack/component.json new file mode 100644 index 0000000..556fc0e --- /dev/null +++ b/lib/templates/pack/component.json @@ -0,0 +1,9 @@ +{ + "name": "@pack@", + "version": "1.0.0", + "jetVersion": "6.0.0", + "type": "pack", + "displayName": "A user friendly, translatable name of the pack.", + "description": "A translatable high-level description for the pack.", + "dependencies": {} +} diff --git a/lib/util.js b/lib/util.js index 2c2cd4f..6ad8d1f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -29,6 +29,7 @@ const url = require('url'); /* Oracle */ const CONSTANTS = require('./constants'); + /** * # Utils * @@ -336,9 +337,11 @@ util.log.warning = function (message) { * * @public * @param {string} message + * @param {boolean} [omitErrorString=false] */ -util.log.error = function (message) { - util.log(`\x1b[31mError: ${message}\x1b[0m`); +util.log.error = function (message, omitErrorString) { + const omit = typeof omitErrorString === 'boolean' ? omitErrorString : false; + util.log(`\x1b[31m${omit ? '' : 'Error: '}${message}\x1b[0m`); process.exit(1); }; @@ -468,31 +471,6 @@ function _addFileListPathPrefix(match, dest, cwd, rename) { const srcMatch = _mapFileNamePrefix(match, cwd); return _processMatch(srcMatch, destMatch); } - -/** - * ## getDirectories - * - * @public - * @param {string} source - * @returns {array} - */ -util.getDirectories = function (source) { - return fs.readdirSync(source).filter((file) => { // eslint-disable-line - return util.isDirectory(path.join(source, file)); - }); -}; - -/** - * ## isDirectory - * - * @public - * @param {string} source - * @returns {boolean} - */ -util.isDirectory = function (source) { - return fs.statSync(source).isDirectory(); -}; - /** * ## getFileList * Obtain the file list array of objects contain src and dest pairs @@ -530,9 +508,12 @@ function _buildTypeMatch(file) { * @returns {array} */ util.getDirectories = function (source) { - return fs.readdirSync(source).filter((sourceItem) => { // eslint-disable-line - return util.isDirectory(path.join(source, sourceItem)); - }); + if (fs.existsSync(source)) { + return fs.readdirSync(source).filter((sourceItem) => { // eslint-disable-line + return util.isDirectory(path.join(source, sourceItem)); + }); + } + return []; }; /** @@ -1097,3 +1078,13 @@ util.fetchComponentMetadata = function (componentName) { }); }); }; + +util.isPathCCA = function (filePath) { + const jetCCA = new RegExp(CONSTANTS.JET_COMPOSITE_DIRECTORY); + return jetCCA.test(filePath); +}; + +util.getCCANameFromPath = function (filePath) { + const rootDir = path.join('js', CONSTANTS.JET_COMPOSITE_DIRECTORY); + return path.basename(path.parse(path.relative(rootDir, filePath)).dir); +}; diff --git a/lib/utils.exchange.js b/lib/utils.exchange.js index ddbc91d..0022a7d 100644 --- a/lib/utils.exchange.js +++ b/lib/utils.exchange.js @@ -76,7 +76,6 @@ function _getEnvironment() { const jetCompsDir = `./${CONSTANTS.JET_COMPONENTS_DIRECTORY}`; const compJson = CONSTANTS.JET_COMPONENT_JSON; const componentsDirectories = util.getDirectories(jetCompsDir); - const environment = {}; componentsDirectories.forEach((componentDir) => { const componentJsonPath = path.join(jetCompsDir, componentDir, compJson); @@ -236,6 +235,10 @@ exchangeUtils.getAccessToken = function (user, pass) { responseBody += respBody; }); response.on('end', () => { + if (util.isVerbose()) { + util.log('Access token:'); + util.log(responseBody); + } util.checkForHttpErrors(response, responseBody); resolve(responseBody); }); diff --git a/oraclejet-tooling.js b/oraclejet-tooling.js index 37881c0..ab04b18 100644 --- a/oraclejet-tooling.js +++ b/oraclejet-tooling.js @@ -26,6 +26,7 @@ const CONSTANTS = require('./lib/constants'); 'clean', 'config', CONSTANTS.API_TASKS.CONFIGURE, + CONSTANTS.API_TASKS.CREATE, CONSTANTS.API_TASKS.LIST, CONSTANTS.API_TASKS.PUBLISH, CONSTANTS.API_TASKS.REMOVE, diff --git a/package.json b/package.json index 8cce2af..a64cd3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@oracle/oraclejet-tooling", - "version": "5.2.0", + "version": "6.0.0", "license": "UPL-1.0", "description": "Programmatic API to build and serve Oracle JET web and mobile applications", "keywords": [ @@ -41,6 +41,5 @@ }, "devDependencies": { "eslint": "~3.19.0" - }, - "jetdocversion": "520" + } } diff --git a/test/serve.js b/test/serve.js index 9aab78e..f2fac44 100644 --- a/test/serve.js +++ b/test/serve.js @@ -47,15 +47,6 @@ describe('Serve Tests: ojet.serve.<>', function () }); }); - it ('validatePlatform - non-existing one', () => - { - assert.throws(() => - { - ojet.config.loadOraclejetConfig("SpacePlatform"); - valid.platform('SpacePlatform') - }); - }); - it ('validateBuildType - not filled = > debug', () => { assert(valid.buildType({buildType: undefined}) == 'dev'); diff --git a/test/utilTests.js b/test/utilTests.js new file mode 100644 index 0000000..a4f4019 --- /dev/null +++ b/test/utilTests.js @@ -0,0 +1,59 @@ +/** + Copyright (c) 2015, 2018, Oracle and/or its affiliates. + The Universal Permissive License (UPL), Version 1.0 +*/ +/** + Copyright (c) 2015, 2018, Oracle and/or its affiliates. + The Universal Permissive License (UPL), Version 1.0 +*/ +var env = process.env, + assert = require('assert'), + config = require('@oracle/oraclejet-tooling/lib/config'), + ojetUtil = require('@oracle/oraclejet-tooling/lib/util'), + _ = require('lodash'); + +describe("Config Test", function () +{ + it("Util get file list", function(){ + const fileListSrc = [ { cwd: 'themes', src: [ 'alta/web/**/*pressed_selected.svg' ], dest: 'themes' } ]; + assert(_.isEqual(2, ojetUtil.getFileList('dev', fileListSrc).length)); + }); + + it("Util get jet version", function(){ + const jetversion = ojetUtil.getJETVersion(); + assert(jetversion == require('@oracle/oraclejet-tooling/package.json').version); + }); + + it("Util get all themes", function(){ + config(); + config.loadOraclejetConfig('web'); + const themes = ojetUtil.getAllThemes(); + assert(themes.length == 0); + }); + + it("Util isCCaSassFile expect false", function(){ + assert(ojetUtil.isCcaSassFile('testApp/themes/alta/web/alta.css') === false); + }); + + it("Util isCCaSassFile expect true", function(){ + assert(ojetUtil.isCcaSassFile('jet-composites/mytheme.css') == true); + }); + + it("Util read path mapping", function(){ + config(); + config.loadOraclejetConfig('web'); + const map = ojetUtil.readPathMappingJson(); + assert(map.hasOwnProperty('cdns')); + assert(map.hasOwnProperty('libs')); + assert(map.use === 'local' || map.use === 'cdn'); + }); + + it("Util get lib version", function(){ + const versions = ojetUtil.getLibVersionsObj(); + assert(versions.ojs === require('@oracle/oraclejet-tooling/package.json').version); + assert(versions.ojL10n === versions.ojs); + assert(versions.ojL10n === versions.ojtranslations); + assert(versions.jquery === require('jquery/package.json').version); + }); +}); +