├── runtime ├── register.js └── index.js ├── .eslintignore ├── roc.png ├── log ├── index.js ├── large.js ├── small.js └── default │ ├── index.js │ ├── large.js │ └── small.js ├── roc-new.gif ├── test ├── configuration │ ├── fixtures │ │ ├── roc.empty.config.js │ │ ├── roc.simple.config.js │ │ ├── roc.complex.config.js │ │ └── roc.syntax-error.config.js.ignore │ └── addRaw.js ├── require │ └── fixtures │ │ └── createResolveRequest │ │ └── node_modules │ │ ├── a │ │ └── index.js │ │ └── d │ │ └── index.js ├── documentation │ ├── fixtures │ │ ├── projects │ │ │ ├── complex │ │ │ │ ├── node_modules │ │ │ │ │ └── noop3 │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ └── package.json │ │ │ │ ├── plugins │ │ │ │ │ ├── a │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ └── index.js │ │ │ │ │ └── b │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ └── index.js │ │ │ │ ├── packages │ │ │ │ │ ├── a │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ └── index.js │ │ │ │ │ └── b │ │ │ │ │ │ ├── package.json │ │ │ │ │ │ └── index.js │ │ │ │ ├── docs │ │ │ │ │ ├── Configuration.md │ │ │ │ │ ├── Settings.md │ │ │ │ │ ├── Extensions.md │ │ │ │ │ ├── Actions.md │ │ │ │ │ ├── Dependencies.md │ │ │ │ │ └── Hooks.md │ │ │ │ ├── package.json │ │ │ │ └── ROC.md │ │ │ └── empty │ │ │ │ ├── docs │ │ │ │ ├── Actions.md │ │ │ │ ├── Configuration.md │ │ │ │ ├── Dependencies.md │ │ │ │ ├── Settings.md │ │ │ │ ├── Extensions.md │ │ │ │ └── Hooks.md │ │ │ │ ├── packages │ │ │ │ ├── a │ │ │ │ │ ├── package.json │ │ │ │ │ └── index.js │ │ │ │ └── b │ │ │ │ │ ├── package.json │ │ │ │ │ └── index.js │ │ │ │ ├── plugins │ │ │ │ ├── a │ │ │ │ │ ├── package.json │ │ │ │ │ └── index.js │ │ │ │ └── b │ │ │ │ │ ├── package.json │ │ │ │ │ └── index.js │ │ │ │ ├── package.json │ │ │ │ └── ROC.md │ │ └── getContext.js │ ├── helpers │ │ ├── pad.js │ │ ├── addPadding.js │ │ ├── toCliOption.js │ │ └── getDefaultValue.js │ ├── markdown │ │ ├── createReadme.js │ │ ├── hooksToMarkdown.js │ │ ├── actionsToMarkdown.js │ │ ├── dependenciesToMarkdown.js │ │ ├── settingsToMarkdown.js │ │ ├── commandsToMarkdown.js │ │ ├── configurationToMarkdown.js │ │ └── extensionsToMarkdown.js │ └── text │ │ └── settingsToText.js ├── commands │ ├── init │ │ └── generateTemplate │ │ │ └── fixtures │ │ │ ├── previousAnswers │ │ │ ├── template │ │ │ │ └── test-input │ │ │ └── roc.setup.js │ │ │ ├── simple │ │ │ ├── template │ │ │ │ └── test-input │ │ │ └── roc.setup.js │ │ │ ├── inCondition │ │ │ ├── template │ │ │ │ └── test-input │ │ │ └── roc.setup.js │ │ │ └── alwaysSet │ │ │ ├── template │ │ │ └── test-input │ │ │ └── roc.setup.js │ └── helpers │ │ └── github.js ├── context │ ├── fixtures │ │ ├── projects │ │ │ ├── init │ │ │ │ ├── package.json │ │ │ │ └── roc.config.js │ │ │ └── actions │ │ │ │ ├── package.json │ │ │ │ └── roc.config.js │ │ ├── extensions │ │ │ └── buildExtensionTree │ │ │ │ ├── packages │ │ │ │ ├── a │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ └── b │ │ │ │ │ ├── index.js │ │ │ │ │ └── package.json │ │ │ │ └── plugins │ │ │ │ ├── a │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ │ │ └── b │ │ │ │ ├── index.js │ │ │ │ └── package.json │ │ └── defaultState.js │ ├── extensions │ │ ├── steps │ │ │ └── runPostInits.js │ │ └── helpers │ │ │ └── updateExtensions.js │ └── initContext.js ├── cli │ ├── fixtures │ │ └── runCli │ │ │ └── projects │ │ │ └── a │ │ │ ├── packages │ │ │ └── a │ │ │ │ ├── package.json │ │ │ │ └── index.js │ │ │ └── package.json │ └── commands │ │ └── helpers │ │ ├── isCommandGroup.js │ │ └── isCommand.js ├── helpers │ ├── fixtures │ │ ├── lazyFunctionRequire │ │ │ ├── 1 │ │ │ │ └── index.js │ │ │ └── 2 │ │ │ │ └── index.js │ │ └── validRocProject │ │ │ ├── 1 │ │ │ └── package.json │ │ │ └── 2 │ │ │ └── package.json │ ├── fileExists.js │ ├── folderExists.js │ ├── getPackageJSON.js │ ├── merge.js │ ├── validRocProject.js │ ├── keyboardDistance.js │ ├── getAbsolutePath.js │ ├── getRocPluginDependencies.js │ ├── getRocPackageDependencies.js │ ├── getSuggestions.js │ ├── lazyFunctionRequire.js │ └── onProperty.js ├── .eslintrc ├── converters │ ├── toString.js │ ├── toObject.js │ ├── toInteger.js │ ├── toArray.js │ ├── toBoolean.js │ ├── toRegExp.js │ └── convert.js ├── validation │ ├── validators │ │ ├── isPromise.js │ │ ├── isPath.js │ │ ├── isRegExp.js │ │ ├── isString.js │ │ ├── isInteger.js │ │ ├── isFunction.js │ │ ├── isBoolean.js │ │ ├── isArray.js │ │ ├── isObject.js │ │ ├── oneOf.js │ │ ├── required.js │ │ └── notEmpty.js │ └── helpers │ │ ├── isValid.js │ │ └── createInfoObject.js ├── hooks │ └── manageHooks.js ├── utils.js └── execute │ └── index.js ├── docs ├── assets │ ├── core.png │ ├── overview.png │ ├── extensions.png │ ├── large_log.png │ └── small_log.png ├── guides │ └── CreateTemplate.md ├── README.md ├── default │ └── Hooks.md └── DocumentationGeneration.md ├── src ├── log │ ├── default │ │ ├── index.js │ │ ├── large.js │ │ └── small.js │ ├── index.js │ ├── initLogSmall.js │ └── helpers │ │ └── labels.js ├── context │ ├── extensions │ │ ├── helpers │ │ │ ├── buildList.js │ │ │ ├── ExtensionError.js │ │ │ └── updateExtensions.js │ │ ├── steps │ │ │ ├── runPostInits.js │ │ │ └── processNormalExports.js │ │ └── buildExtensionTree.js │ ├── helpers │ │ ├── manageContext.js │ │ ├── validateAndUpdateSettings.js │ │ └── verifyConfigurationStructure.js │ ├── buildContext.js │ └── dependencies │ │ ├── verifyInstalledProjectDependencies.js │ │ └── verifyRequiredDependencies.js ├── execute │ ├── helpers │ │ ├── getCommand.js │ │ ├── ExecuteError.js │ │ ├── getEnv.js │ │ └── spawnCommandSync.js │ ├── executeSyncExit.js │ ├── executeSync.js │ └── index.js ├── helpers │ ├── getUnmanagedObject.js │ ├── lazyFunctionRequire.js │ ├── merge.js │ ├── onProperty.js │ ├── getRocPluginDependencies.js │ ├── getRocPackageDependencies.js │ ├── getPackageJSON.js │ ├── index.js │ ├── getAbsolutePath.js │ ├── fileExists.js │ ├── folderExists.js │ ├── objectToArray.js │ ├── validRocProject.js │ └── getSuggestions.js ├── require │ ├── createPathRegExp.js │ ├── utils │ │ └── generateDependencies.js │ ├── manageResolveRequest.js │ ├── verifyInstalledDependencies.js │ ├── patchResolveFilename.js │ └── manageDependencies.js ├── converters │ ├── index.js │ ├── toString.js │ ├── toInteger.js │ ├── toObject.js │ ├── toBoolean.js │ ├── toRegExp.js │ ├── toArray.js │ ├── convert.js │ └── automatic.js ├── documentation │ ├── helpers │ │ ├── pad.js │ │ ├── toCliOption.js │ │ ├── addPadding.js │ │ └── getDefaultValue.js │ └── markdown │ │ ├── helpers │ │ └── createStatefulAnchor.js │ │ └── configurationToMarkdown.js ├── validation │ ├── helpers │ │ ├── getInfoObject.js │ │ ├── throwValidationError.js │ │ ├── createInfoObject.js │ │ └── isValid.js │ ├── validators │ │ ├── index.js │ │ ├── isPromise.js │ │ ├── isFunction.js │ │ ├── isRegExp.js │ │ ├── isBoolean.js │ │ ├── isInteger.js │ │ ├── isString.js │ │ ├── isPath.js │ │ ├── required.js │ │ ├── notEmpty.js │ │ ├── isArray.js │ │ ├── oneOf.js │ │ └── isObject.js │ ├── validateSettingsWrapper.js │ └── validateSettings.js ├── cli │ ├── commands │ │ ├── helpers │ │ │ ├── isCommandGroup.js │ │ │ ├── isCommand.js │ │ │ └── extractCommand.js │ │ ├── getCommandArgumentsAsString.js │ │ ├── documentation │ │ │ └── createTable.js │ │ ├── getMappings.js │ │ └── getDefaultOptions.js │ └── processArguments.js ├── commands │ ├── init │ │ ├── generateTemplate │ │ │ ├── getGitUser.js │ │ │ ├── evaluate.js │ │ │ ├── collect.js │ │ │ ├── filter.js │ │ │ ├── ask.js │ │ │ └── options.js │ │ └── githubHelpers.js │ └── lock.js ├── runtime │ └── index.js ├── configuration │ ├── manageSettings.js │ └── addRaw.js ├── index.js └── hooks │ ├── manageHooks.js │ └── runHook.js ├── .gitignore ├── .codeclimate.yml ├── converters └── index.js ├── .istanbul.yml ├── validators └── index.js ├── appveyor.yml ├── bin ├── cli.js └── index.js ├── esdoc.json ├── .babelrc ├── .eslintrc ├── scripts ├── generate-documentation.js └── post-install.js ├── .travis.yml ├── LICENSE └── .editorconfig /runtime/register.js: -------------------------------------------------------------------------------- 1 | require('./')(); 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | esdocs 3 | coverage 4 | -------------------------------------------------------------------------------- /roc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc/HEAD/roc.png -------------------------------------------------------------------------------- /log/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/log').default; 2 | -------------------------------------------------------------------------------- /roc-new.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc/HEAD/roc-new.gif -------------------------------------------------------------------------------- /runtime/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/runtime').default; 2 | -------------------------------------------------------------------------------- /test/configuration/fixtures/roc.empty.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /log/large.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/log/initLogLarge').default; 2 | -------------------------------------------------------------------------------- /log/small.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/log/initLogSmall').default; 2 | -------------------------------------------------------------------------------- /log/default/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../../lib/log/default').default; 2 | -------------------------------------------------------------------------------- /docs/assets/core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc/HEAD/docs/assets/core.png -------------------------------------------------------------------------------- /docs/assets/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc/HEAD/docs/assets/overview.png -------------------------------------------------------------------------------- /log/default/large.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../../lib/log/default/large').default; 2 | -------------------------------------------------------------------------------- /log/default/small.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../../lib/log/default/small').default; 2 | -------------------------------------------------------------------------------- /src/log/default/index.js: -------------------------------------------------------------------------------- 1 | import initLog from '../'; 2 | 3 | export default initLog(); 4 | -------------------------------------------------------------------------------- /test/require/fixtures/createResolveRequest/node_modules/a/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'a'; 2 | -------------------------------------------------------------------------------- /test/require/fixtures/createResolveRequest/node_modules/d/index.js: -------------------------------------------------------------------------------- 1 | module.exports = 'd'; 2 | -------------------------------------------------------------------------------- /docs/assets/extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc/HEAD/docs/assets/extensions.png -------------------------------------------------------------------------------- /docs/assets/large_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc/HEAD/docs/assets/large_log.png -------------------------------------------------------------------------------- /docs/assets/small_log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocjs/roc/HEAD/docs/assets/small_log.png -------------------------------------------------------------------------------- /src/log/default/large.js: -------------------------------------------------------------------------------- 1 | import initLogLarge from '../initLogLarge'; 2 | 3 | export default initLogLarge(); 4 | -------------------------------------------------------------------------------- /src/log/default/small.js: -------------------------------------------------------------------------------- 1 | import initLogSmall from '../initLogSmall'; 2 | 3 | export default initLogSmall(); 4 | -------------------------------------------------------------------------------- /test/configuration/fixtures/roc.simple.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | settings: { 3 | }, 4 | }; 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/node_modules/noop3/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function noop() {}; 2 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/previousAnswers/template/test-input: -------------------------------------------------------------------------------- 1 | {{{ hello }}} 2 | {{{ welcome }}} 3 | -------------------------------------------------------------------------------- /test/context/fixtures/projects/init/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-project-init", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/docs/Actions.md: -------------------------------------------------------------------------------- 1 | # Actions for `empty` 2 | 3 | __No actions available.__ 4 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/simple/template/test-input: -------------------------------------------------------------------------------- 1 | {{{ hello }}} 2 | {{{ and }}} 3 | {{{ welcome }}} 4 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/packages/a/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | }, 4 | }; 5 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/packages/b/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | }, 4 | }; 5 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/plugins/a/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | }, 4 | }; 5 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/plugins/b/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | }, 4 | }; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.log 3 | .DS_Store 4 | lib 5 | esdocs 6 | coverage 7 | .idea 8 | .nyc_output 9 | _output 10 | -------------------------------------------------------------------------------- /test/cli/fixtures/runCli/projects/a/packages/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-package-a", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/inCondition/template/test-input: -------------------------------------------------------------------------------- 1 | {{#if_eq hello 'welcome'}}{{{ hello }}}{{/if_eq}} 2 | -------------------------------------------------------------------------------- /test/context/fixtures/projects/actions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-project-actions", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/helpers/fixtures/lazyFunctionRequire/1/index.js: -------------------------------------------------------------------------------- 1 | global.lazyFunctionRequire = 1; 2 | 3 | module.exports = (...args) => args; 4 | -------------------------------------------------------------------------------- /test/helpers/fixtures/lazyFunctionRequire/2/index.js: -------------------------------------------------------------------------------- 1 | global.lazyFunctionRequire = 2; 2 | 3 | export default (...args) => args; 4 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/alwaysSet/template/test-input: -------------------------------------------------------------------------------- 1 | {{{ hello }}} 2 | {{{ destDirName }}} 3 | {{{ inPlace }}} 4 | -------------------------------------------------------------------------------- /test/context/fixtures/projects/init/roc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | init: () => {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/plugins/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-plugin-a", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/plugins/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-plugin-b", 3 | "version": "1.2.3" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/packages/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-package-a", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/packages/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-package-b", 3 | "version": "1.2.3" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/plugins/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-plugin-a", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/plugins/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-plugin-b", 3 | "version": "1.2.3" 4 | } 5 | -------------------------------------------------------------------------------- /test/configuration/fixtures/roc.complex.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | settings: { 3 | }, 4 | commands: { 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/node_modules/noop3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "noop3", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/packages/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-package-a", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/packages/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-package-b", 3 | "version": "1.2.3" 4 | } 5 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/packages/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-package-a", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/packages/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-package-b", 3 | "version": "1.2.3" 4 | } 5 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/plugins/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-plugin-a", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/context/fixtures/extensions/buildExtensionTree/plugins/b/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roc-plugin-b", 3 | "version": "1.2.3" 4 | } 5 | -------------------------------------------------------------------------------- /test/context/fixtures/projects/actions/roc.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | actions: undefined, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /test/helpers/fixtures/validRocProject/1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "roc": { 3 | "packages": [ 4 | "something" 5 | ] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/configuration/fixtures/roc.syntax-error.config.js.ignore: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | settings: { 3 | port: 8080 4 | on: false 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | eslint: 4 | enabled: true 5 | fixme: 6 | enabled: true 7 | ratings: 8 | paths: 9 | - "**.js" 10 | exclude_paths: [] 11 | -------------------------------------------------------------------------------- /test/helpers/fixtures/validRocProject/2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "roc-package-web": "1.0.0" 4 | }, 5 | "roc": { 6 | "packages": [] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /converters/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file makes it easier to import the converters. 3 | * Can use "roc/converters" to only get the converters. 4 | */ 5 | module.exports = require('../lib/converters'); 6 | -------------------------------------------------------------------------------- /src/context/extensions/helpers/buildList.js: -------------------------------------------------------------------------------- 1 | export default function buildList(elements) { 2 | return elements.map( 3 | (element) => ` - ${element}` 4 | ).join('\n') + '\n\n'; 5 | } 6 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | instrumentation: 2 | root: src 3 | include-all-sources: true 4 | reporting: 5 | print: summary 6 | reports: 7 | - html 8 | - lcov 9 | dir: coverage 10 | -------------------------------------------------------------------------------- /validators/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file makes it easier to import the validators. 3 | * Can use "roc/validators" to only get the validatiors. 4 | */ 5 | module.exports = require('../lib/validation/validators'); 6 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/docs/Configuration.md: -------------------------------------------------------------------------------- 1 | # Config for `empty` 2 | 3 | Configuration that can be defined in `roc.config.js`, other than settings and project. 4 | 5 | __No config available.__ 6 | -------------------------------------------------------------------------------- /src/execute/helpers/getCommand.js: -------------------------------------------------------------------------------- 1 | import shellEscape from 'shell-escape'; 2 | 3 | export default function getCommand(command, args = []) { 4 | return args.length > 0 ? `${command} ${shellEscape(args)}` : command; 5 | } 6 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/alwaysSet/roc.setup.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prompts: { 3 | hello: { 4 | type: 'input', 5 | required: true, 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/inCondition/roc.setup.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prompts: { 3 | hello: { 4 | type: 'input', 5 | required: true, 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /test/cli/fixtures/runCli/projects/a/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-project", 3 | "version": "1.0.0", 4 | "roc": { 5 | "packages": [ 6 | "./packages/a/index.js" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/plugins/a/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | roc: { 5 | plugins: [require.resolve(path.join(__dirname, '..', 'b', 'index.js'))], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/packages/a/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | roc: { 5 | packages: [require.resolve(path.join(__dirname, '..', 'b', 'index.js'))], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/plugins/a/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | roc: { 5 | plugins: [require.resolve(path.join(__dirname, '..', 'b', 'index.js'))], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | 6 | "rules": { 7 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], 8 | "global-require": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/documentation/fixtures/getContext.js: -------------------------------------------------------------------------------- 1 | import initContext from '../../../src/context/initContext'; 2 | 3 | export default (projectPath, commands) => initContext({ 4 | commands, 5 | directory: projectPath, 6 | runtime: false, 7 | }); 8 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/docs/Configuration.md: -------------------------------------------------------------------------------- 1 | # Config for `complex` 2 | 3 | Configuration that can be defined in `roc.config.js`, other than settings and project. 4 | 5 | ## `webpack` 6 | A description! 7 | 8 | __Extensions__: roc-plugin-b 9 | -------------------------------------------------------------------------------- /src/log/index.js: -------------------------------------------------------------------------------- 1 | import initLogLarge from './initLogLarge'; 2 | import initLogSmall from './initLogSmall'; 3 | 4 | export default function initLog(name, version) { 5 | return { 6 | large: initLogLarge(name, version), 7 | small: initLogSmall(), 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /test/cli/fixtures/runCli/projects/a/packages/a/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | config: { 4 | settings: { 5 | group1: { 6 | port: 3000, 7 | }, 8 | }, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/helpers/getUnmanagedObject.js: -------------------------------------------------------------------------------- 1 | import { isFunction } from 'lodash'; 2 | 3 | export default function getUnmanagedObject(validator) { 4 | if (validator && isFunction(validator)) { 5 | return !validator(null, true).unmanagedObject; 6 | } 7 | 8 | return true; 9 | } 10 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/docs/Dependencies.md: -------------------------------------------------------------------------------- 1 | # Dependencies for `empty` 2 | 3 | The dependencies that are available in the project. 4 | 5 | ## Exported 6 | Nothing is exported. 7 | 8 | ## Requires 9 | Nothing is required. 10 | 11 | ## Uses 12 | Nothing is listed as used. 13 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/packages/b/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | config: { 4 | settings: { 5 | group: { 6 | value2: true, 7 | }, 8 | }, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/plugins/b/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | config: { 4 | settings: { 5 | group: { 6 | value2: false, 7 | }, 8 | }, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/require/createPathRegExp.js: -------------------------------------------------------------------------------- 1 | import { sep } from 'path'; 2 | 3 | export default function createPathRegExp(path, flags) { 4 | if (sep === '\\') { 5 | // eslint-disable-next-line 6 | path = path.replace(/\\/g, '\\\\'); 7 | } 8 | 9 | return new RegExp(path, flags); 10 | } 11 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "empty", 3 | "version": "1.0.0", 4 | "roc": { 5 | "packages": [ 6 | "./packages/a/index.js" 7 | ], 8 | "plugins": [ 9 | "./plugins/a/index.js" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | environment: 6 | matrix: 7 | - nodejs_version: "6" 8 | 9 | install: 10 | - ps: Install-Product node $env:nodejs_version 11 | - set CI=true 12 | - npm install 13 | 14 | test_script: 15 | - node --version 16 | - npm --version 17 | 18 | build: off 19 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | const packageJSON = require('../package.json'); 2 | const runCli = require('../lib/cli/runCli').default; 3 | const commands = require('../lib/commands').default; 4 | 5 | runCli({ 6 | info: { 7 | version: packageJSON.version, 8 | name: packageJSON.name, 9 | }, 10 | commands, 11 | }); 12 | -------------------------------------------------------------------------------- /src/converters/index.js: -------------------------------------------------------------------------------- 1 | export automatic from './automatic'; 2 | export convert from './convert'; 3 | export toArray from './toArray'; 4 | export toBoolean from './toBoolean'; 5 | export toInteger from './toInteger'; 6 | export toObject from './toObject'; 7 | export toRegExp from './toRegExp'; 8 | export toString from './toString'; 9 | -------------------------------------------------------------------------------- /src/helpers/lazyFunctionRequire.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Function that can be used to make require lazy if the default export is a function. 3 | */ 4 | export default function lazyFunctionRequire(fn) { 5 | return (id) => (...args) => { 6 | const mod = fn(id); 7 | return mod.__esModule ? mod.default(...args) : mod(...args); 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "complex", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "noop3": "1.0.0" 6 | }, 7 | "roc": { 8 | "packages": [ 9 | "./packages/a/index.js" 10 | ], 11 | "plugins": [ 12 | "./plugins/a/index.js" 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "./src", 3 | "destination": "./esdocs", 4 | "plugins": [{ 5 | "name": "esdoc-es7-plugin" 6 | }, { 7 | "name": "esdoc-importpath-plugin", 8 | "option": { 9 | "replaces": [ 10 | {"from": "^src/", "to": "lib/"} 11 | ] 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/documentation/helpers/pad.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns a string to pad with. 3 | * 4 | * @param {number} length - The desired length of the generated string. 5 | * @param {string} [character=" "] - The character to repat. 6 | * 7 | * @returns {string} - A string matching the input. 8 | */ 9 | export default function pad(length, character = ' ') { 10 | return Array(length + 1).join(character); 11 | } 12 | -------------------------------------------------------------------------------- /test/documentation/helpers/pad.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import pad from '../../../src/documentation/helpers/pad'; 4 | 5 | describe('documentation', () => { 6 | describe('helpers', () => { 7 | describe('pad', () => { 8 | it('should pad correctly', () => { 9 | expect(pad(4, '-')).toBe('----'); 10 | }); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/validation/helpers/getInfoObject.js: -------------------------------------------------------------------------------- 1 | import { isFunction } from 'lodash'; 2 | 3 | import createInfoObject from './createInfoObject'; 4 | 5 | export default function getInfoObject(validator) { 6 | if (validator) { 7 | return isFunction(validator) ? 8 | validator(null, true) : 9 | { type: validator.toString() }; 10 | } 11 | 12 | return createInfoObject(); 13 | } 14 | -------------------------------------------------------------------------------- /src/converters/toString.js: -------------------------------------------------------------------------------- 1 | import { isString } from 'lodash'; 2 | 3 | /** 4 | * Given an input the function will return a string. 5 | * 6 | * @param {object} input - The input to be converted. 7 | * 8 | * @returns {number} - The converted result. 9 | */ 10 | export default function toString(input) { 11 | if (isString(input)) { 12 | return input; 13 | } 14 | 15 | return input.toString(); 16 | } 17 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/simple/roc.setup.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prompts: { 3 | hello: { 4 | type: 'input', 5 | required: true, 6 | }, 7 | and: { 8 | type: 'input', 9 | required: true, 10 | }, 11 | welcome: { 12 | type: 'input', 13 | required: true, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/documentation/helpers/toCliOption.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Takes a configuration path array and converters it to a cli option. 3 | * 4 | * @param {string[]} configPaths - The configuration path, a array consisting of properties. 5 | * 6 | * @returns {string} - The cli option to set the given configuration option. 7 | */ 8 | export default function toCliOption(configPaths) { 9 | return `--${configPaths.join('-')}`; 10 | } 11 | -------------------------------------------------------------------------------- /src/cli/commands/helpers/isCommandGroup.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject } from 'lodash'; 2 | 3 | // The property "command" is special and can not be used as a command 4 | export default function isCommandGroup(commandsObject) { 5 | return (potentialGroup) => 6 | potentialGroup !== '__meta' && 7 | isPlainObject(commandsObject[potentialGroup]) && 8 | commandsObject[potentialGroup].command === undefined; 9 | } 10 | -------------------------------------------------------------------------------- /src/converters/toInteger.js: -------------------------------------------------------------------------------- 1 | import { isInteger } from 'lodash'; 2 | 3 | /** 4 | * Given an input the function will return an number. 5 | * 6 | * @param {object} input - The input to be converted. 7 | * 8 | * @returns {number} - The converted result. 9 | */ 10 | export default function toInteger(input) { 11 | if (isInteger(input)) { 12 | return input; 13 | } 14 | 15 | return parseInt(input, 10); 16 | } 17 | -------------------------------------------------------------------------------- /test/documentation/helpers/addPadding.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import addPadding from '../../../src/documentation/helpers/addPadding'; 4 | 5 | describe('documentation', () => { 6 | describe('helpers', () => { 7 | describe('addPadding', () => { 8 | it('should add correct spacing', () => { 9 | expect(addPadding('Hello', 6)).toBe('Hello '); 10 | }); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/helpers/merge.js: -------------------------------------------------------------------------------- 1 | import mergeOptions from 'merge-options'; 2 | 3 | /** 4 | * Merges two configuration objects. 5 | * 6 | * @param {!Object} a - Configuration object to base the merge on. 7 | * @param {!Object} b - Configuration object that is merged into the first, overwriting the first one. 8 | * 9 | * @returns {Object} - The merged configuration object 10 | */ 11 | export default function merge(a, b) { 12 | return mergeOptions(a, b); 13 | } 14 | -------------------------------------------------------------------------------- /test/converters/toString.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toString from '../../src/converters/toString'; 4 | 5 | describe('converters', () => { 6 | describe('toString', () => { 7 | it('should convert input to string', () => { 8 | expect(toString(true)).toEqual('true'); 9 | expect(toString(123)).toEqual('123'); 10 | 11 | expect(toString('true')).toEqual('true'); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/plugins/b/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | config: { 4 | settings: { 5 | group: { 6 | value2: false, 7 | }, 8 | }, 9 | webpack: {}, 10 | }, 11 | 12 | meta: { 13 | webpack: { 14 | description: 'A description!', 15 | }, 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /test/documentation/helpers/toCliOption.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toCliOption from '../../../src/documentation/helpers/toCliOption'; 4 | 5 | describe('documentation', () => { 6 | describe('helpers', () => { 7 | describe('toCliOption', () => { 8 | it('should return a option correct', () => { 9 | expect(toCliOption(['build', 'option2'])).toBe('--build-option2'); 10 | }); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/require/utils/generateDependencies.js: -------------------------------------------------------------------------------- 1 | export default function generateDependencies(packageJSON, toExport) { 2 | let dependencies = {}; 3 | Object.keys(packageJSON.dependencies).forEach((dependency) => { 4 | if (toExport.indexOf(dependency) > -1) { 5 | dependencies = { 6 | ...dependencies, 7 | [dependency]: packageJSON.dependencies[dependency], 8 | }; 9 | } 10 | }); 11 | 12 | return dependencies; 13 | } 14 | -------------------------------------------------------------------------------- /docs/guides/CreateTemplate.md: -------------------------------------------------------------------------------- 1 | # Creating a template 2 | 3 | A [template](https://github.com/rocjs/roc/blob/master/docs/Templates.md) in Roc is a git repository that contains at least a `package.json` and a `template/` directory. It is also possible to define a `roc.setup.js` or `roc.setup.json` for more control over how the template is instantiated. 4 | 5 | For a complete overview over what can be defined in the `roc.setup.js(on)` file [see here](https://github.com/rocjs/roc/blob/master/docs/Templates.md#structure). 6 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/docs/Settings.md: -------------------------------------------------------------------------------- 1 | # Settings for `empty` 2 | 3 | ## Group 4 | 5 | | Name | Description | Path | CLI option | Default | Type | Required | Can be empty | Extensions | 6 | | ------ | ----------- | ------------ | -------------- | ------- | --------- | -------- | ------------ | --------------------------- | 7 | | value2 | | group.value2 | --group-value2 | `false` | `Unknown` | No | Yes | roc-package-b, roc-plugin-b | 8 | -------------------------------------------------------------------------------- /src/converters/toObject.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject } from 'lodash'; 2 | 3 | /** 4 | * Given an input the function will return an object. 5 | * 6 | * @param {object} input - The input to be converted. 7 | * 8 | * @returns {object} - The converted result. 9 | */ 10 | export default function toObject(input) { 11 | if (isPlainObject(input)) { 12 | return input; 13 | } 14 | try { 15 | return JSON.parse(input); 16 | } catch (error) { 17 | return undefined; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/helpers/onProperty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sorter for an object on a property 3 | * 4 | * @param {string} property - The property to sort on. 5 | * 6 | * @returns {number} - The sort value for that property. 7 | */ 8 | export default function onProperty(property) { 9 | return (a, b) => { 10 | if (a[property] > b[property]) { 11 | return 1; 12 | } 13 | if (a[property] < b[property]) { 14 | return -1; 15 | } 16 | 17 | return 0; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/docs/Settings.md: -------------------------------------------------------------------------------- 1 | # Settings for `complex` 2 | 3 | ## Group 4 | 5 | | Name | Description | Path | CLI option | Default | Type | Required | Can be empty | Extensions | 6 | | ------ | ----------- | ------------ | -------------- | ------- | --------- | -------- | ------------ | --------------------------- | 7 | | value2 | | group.value2 | --group-value2 | `false` | `Unknown` | No | Yes | roc-package-b, roc-plugin-b | 8 | -------------------------------------------------------------------------------- /src/context/extensions/helpers/ExtensionError.js: -------------------------------------------------------------------------------- 1 | export default class ExtensionError extends Error { 2 | constructor(message, extension, version, path) { 3 | super(message); 4 | this.extension = extension; 5 | this.version = version; 6 | this.path = path; 7 | } 8 | 9 | toString() { 10 | return `${this.message}\nOccurred in: ${this.extension}${this.version ? `@${this.version}` : ''}`; 11 | } 12 | 13 | getPath() { 14 | return this.path; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/init/generateTemplate/getGitUser.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | 3 | export default function getGitUser() { 4 | try { 5 | const name = execSync('git config --get user.name'); 6 | const email = execSync('git config --get user.email'); 7 | 8 | return { 9 | name: name && name.toString().trim(), 10 | email: email && email.toString().trim(), 11 | }; 12 | } catch (e) { 13 | // Do nothing 14 | return {}; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/init/generateTemplate/evaluate.js: -------------------------------------------------------------------------------- 1 | import log from '../../../log/default/small'; 2 | 3 | /** 4 | * Evaluate an expression in roc.setup.js(on) in the context of 5 | * prompt answers data. 6 | */ 7 | export default function evaluate(exp, data) { 8 | /* eslint-disable no-new-func */ 9 | const fn = new Function('data', `with (data) { return ${exp}}`); 10 | try { 11 | return fn(data); 12 | } catch (e) { 13 | return log.warn(`Error when evaluating filter condition: ${exp}`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/context/helpers/manageContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Using global variables here to make sure that we can access the values set from different projects. 3 | * This guarantees that the variables will live outside the require cache, something that we need for stability. 4 | */ 5 | global.roc = global.roc || {}; 6 | global.roc.context = global.roc.context || {}; 7 | 8 | export function getContext() { 9 | return global.roc.context; 10 | } 11 | 12 | export function setContext(context) { 13 | global.roc.context = context; 14 | } 15 | -------------------------------------------------------------------------------- /src/documentation/markdown/helpers/createStatefulAnchor.js: -------------------------------------------------------------------------------- 1 | import anchor from 'anchor-markdown-header'; 2 | 3 | export default function createStatefulAnchor(mode = 'github.com') { 4 | const titleCount = {}; 5 | const getTitleCount = (title) => { 6 | if (titleCount[title] !== undefined) { 7 | return ++titleCount[title]; 8 | } 9 | 10 | titleCount[title] = 0; 11 | return titleCount[title]; 12 | }; 13 | 14 | return (title) => anchor(title, mode, getTitleCount(title)); 15 | } 16 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/packages/a/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | roc: { 5 | packages: [require.resolve(path.join(__dirname, '..', 'b', 'index.js'))], 6 | dependencies: { 7 | uses: { 8 | lodash: '~4.0.0', 9 | }, 10 | exports: { 11 | react: '^15.0.0', 12 | }, 13 | requires: { 14 | noop3: '*', 15 | }, 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/documentation/helpers/addPadding.js: -------------------------------------------------------------------------------- 1 | import stripAnsi from 'strip-ansi'; 2 | 3 | import pad from './pad'; 4 | 5 | /** 6 | * Pads a string to a given length with spaces. 7 | * 8 | * @param {string} str - The string to be padded. 9 | * @param {number} length - The desired length of the new string. 10 | * 11 | * @returns {string} - A string matching the input. 12 | */ 13 | export default function addPadding(str, length) { 14 | const string = str || ''; 15 | return string + pad(length - stripAnsi(string).length); 16 | } 17 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/docs/Extensions.md: -------------------------------------------------------------------------------- 1 | # Extensions for `empty` 2 | 3 | The extensions that are used in the project, indirect and direct, in the order that they were added. 4 | 5 | ## Packages 6 | ### roc-package-b — [v1.2.3](https://www.npmjs.com/package/roc-package-b) 7 | 8 | ### roc-package-a — [v1.0.0](https://www.npmjs.com/package/roc-package-a) 9 | 10 | ## Plugins 11 | ### roc-plugin-b — [v1.2.3](https://www.npmjs.com/package/roc-plugin-b) 12 | 13 | ### roc-plugin-a — [v1.0.0](https://www.npmjs.com/package/roc-plugin-a) 14 | -------------------------------------------------------------------------------- /src/converters/toBoolean.js: -------------------------------------------------------------------------------- 1 | import { isBoolean, isString } from 'lodash'; 2 | 3 | /** 4 | * Given an input the function will return a boolean. 5 | * 6 | * @param {object} input - The input to be converted. 7 | * 8 | * @returns {bool} - The converted result. 9 | */ 10 | export default function toBoolean(input) { 11 | if (isBoolean(input)) { 12 | return input; 13 | } 14 | 15 | if (isString(input) && (input === 'true' || input === 'false')) { 16 | return input === 'true'; 17 | } 18 | 19 | return undefined; 20 | } 21 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/docs/Extensions.md: -------------------------------------------------------------------------------- 1 | # Extensions for `complex` 2 | 3 | The extensions that are used in the project, indirect and direct, in the order that they were added. 4 | 5 | ## Packages 6 | ### roc-package-b — [v1.2.3](https://www.npmjs.com/package/roc-package-b) 7 | 8 | ### roc-package-a — [v1.0.0](https://www.npmjs.com/package/roc-package-a) 9 | 10 | ## Plugins 11 | ### roc-plugin-b — [v1.2.3](https://www.npmjs.com/package/roc-plugin-b) 12 | 13 | ### roc-plugin-a — [v1.0.0](https://www.npmjs.com/package/roc-plugin-a) 14 | -------------------------------------------------------------------------------- /src/validation/validators/index.js: -------------------------------------------------------------------------------- 1 | export isArray from './isArray'; 2 | export isBoolean from './isBoolean'; 3 | export isFunction from './isFunction'; 4 | export isInteger from './isInteger'; 5 | export isObject from './isObject'; 6 | export isPath from './isPath'; 7 | export isPromise from './isPromise'; 8 | export isRegExp from './isRegExp'; 9 | export isString from './isString'; 10 | export notEmpty from './notEmpty'; 11 | export oneOf from './oneOf'; 12 | export required from './required'; 13 | 14 | export createInfoObject from '../helpers/createInfoObject'; 15 | -------------------------------------------------------------------------------- /test/commands/init/generateTemplate/fixtures/previousAnswers/roc.setup.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prompts: { 3 | hello: { 4 | type: 'input', 5 | required: true, 6 | }, 7 | welcome: { 8 | type: 'input', 9 | default: (answers) => { 10 | if (answers.hello === 'something') { 11 | return 'this should be a default'; 12 | } 13 | return 'nope'; 14 | }, 15 | required: true, 16 | }, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/docs/Actions.md: -------------------------------------------------------------------------------- 1 | # Actions for `complex` 2 | 3 | ## Actions 4 | * [roc-package-b](#roc-package-b) 5 | * [Generic](#generic) 6 | * [before-clean](#before-clean) 7 | 8 | ## roc-package-b 9 | 10 | ### Generic 11 | 12 | __Connects to extension:__ Not specified 13 | __Connects to hook:__ Not specified 14 | __Have post:__ No 15 | 16 | ### before-clean 17 | 18 | Some __description__. 19 | 20 | __Connects to extension:__ `roc-package-core-dev` 21 | __Connects to hook:__ `before-clean` 22 | __Have post:__ Yes 23 | -------------------------------------------------------------------------------- /test/helpers/fileExists.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import fileExists from '../../src/helpers/fileExists'; 4 | 5 | describe('helpers', () => { 6 | describe('fileExists', () => { 7 | it('should return true if file exists', () => { 8 | expect(fileExists('fileExists.js', __dirname)) 9 | .toBe(true); 10 | }); 11 | 12 | it('should return false if file do not exist', () => { 13 | expect(fileExists('roc.config.js', __dirname)) 14 | .toBe(false); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/docs/Dependencies.md: -------------------------------------------------------------------------------- 1 | # Dependencies for `complex` 2 | 3 | The dependencies that are available in the project. 4 | 5 | ## Exported 6 | ### [react](https://www.npmjs.com/package/react) 7 | __Version__: ^15.0.0 8 | __Extension__: roc-package-a 9 | __Custom resolve function__: No 10 | 11 | ## Requires 12 | ### [noop3](https://www.npmjs.com/package/noop3) 13 | __Version__: * 14 | __Extension__: roc-package-a 15 | 16 | ## Uses 17 | ### roc-package-a 18 | #### [lodash](https://www.npmjs.com/package/lodash) 19 | __Version__: ~4.0.0 20 | -------------------------------------------------------------------------------- /src/converters/toRegExp.js: -------------------------------------------------------------------------------- 1 | import { isRegExp } from 'lodash'; 2 | 3 | /** 4 | * Given an input the function will return a RegExp. 5 | * 6 | * @param {object} input - The input to be converted. 7 | * 8 | * @returns {RegExp} - The converted result. 9 | */ 10 | export default function toRegExp(input) { 11 | if (isRegExp(input)) { 12 | return input; 13 | } 14 | 15 | // Remove potential leading / / and get possible flags 16 | const parsedInput = /^\/?(.*?)(?:\/?|\/([gimuy]*))$/.exec(input); 17 | 18 | return new RegExp(parsedInput[1], parsedInput[2]); 19 | } 20 | -------------------------------------------------------------------------------- /src/runtime/index.js: -------------------------------------------------------------------------------- 1 | import initContext from '../context/initContext'; 2 | import validateAndUpdateSettings from '../context/helpers/validateAndUpdateSettings'; 3 | import { setConfig } from '../configuration/manageConfig'; 4 | 5 | export default function initRuntime({ 6 | verbose = false, 7 | directory = process.cwd(), 8 | projectConfigPath, 9 | } = {}) { 10 | const context = initContext({ 11 | verbose, 12 | directory, 13 | projectConfigPath, 14 | }); 15 | 16 | setConfig( 17 | validateAndUpdateSettings(context).config 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /test/converters/toObject.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toObject from '../../src/converters/toObject'; 4 | 5 | describe('converters', () => { 6 | describe('toObject', () => { 7 | it('should convert input to object', () => { 8 | expect(toObject({ a: 1, b: 'hello' })).toEqual({ a: 1, b: 'hello' }); 9 | expect(toObject('{"a": 1, "b": "hello"}')).toEqual({ a: 1, b: 'hello' }); 10 | }); 11 | 12 | it('should manage invalid input', () => { 13 | expect(toObject('1,2,3')).toEqual(undefined); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/cli/commands/getCommandArgumentsAsString.js: -------------------------------------------------------------------------------- 1 | import getInfoObject from '../../validation/helpers/getInfoObject'; 2 | import objectToArray from '../../helpers/objectToArray'; 3 | 4 | /** 5 | * Generates arguments as an information string. 6 | */ 7 | export default function getCommandArgumentsAsString(command = {}) { 8 | let args = ''; 9 | objectToArray(command.arguments).forEach((argument) => { 10 | const required = getInfoObject(argument.validator).required; 11 | args += required ? ` <${argument.name}>` : ` [${argument.name}]`; 12 | }); 13 | 14 | return args; 15 | } 16 | -------------------------------------------------------------------------------- /test/helpers/folderExists.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import expect from 'expect'; 4 | 5 | import folderExists from '../../src/helpers/folderExists'; 6 | 7 | describe('helpers', () => { 8 | describe('folderExists', () => { 9 | it('should return true if file exists', () => { 10 | expect(folderExists(__dirname)) 11 | .toBe(true); 12 | }); 13 | 14 | it('should return false if file do not exist', () => { 15 | expect(folderExists(join(__dirname, 'nope'))) 16 | .toBe(false); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/execute/helpers/ExecuteError.js: -------------------------------------------------------------------------------- 1 | export default class ExecuteError extends Error { 2 | constructor(message, command, exitCode, stderr, stdout) { 3 | super(message); 4 | this.command = command; 5 | this.exitCode = exitCode; 6 | this.stderr = stderr; 7 | this.stdout = stdout; 8 | } 9 | 10 | getExitCode() { 11 | return this.exitCode; 12 | } 13 | 14 | getCommand() { 15 | return this.command; 16 | } 17 | 18 | getStderr() { 19 | return this.stderr; 20 | } 21 | 22 | getStdout() { 23 | return this.stdout; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/helpers/getRocPluginDependencies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets the Roc plugin dependencies from a `package.json`. 3 | * 4 | * @param {Object} packageJSON - A package.json file to fetch Roc plugin dependencies from. 5 | * 6 | * @returns {string[]} - An array with Roc plugins that exists in the `package.json`. 7 | */ 8 | export default function getRocPluginDependencies(packageJSON) { 9 | return [ 10 | ...Object.keys(packageJSON.dependencies || {}).sort(), 11 | ...Object.keys(packageJSON.devDependencies || {}).sort(), 12 | ] 13 | .filter((dependency) => /^(?:@.*\/)?roc-plugin(-.+)/.test(dependency)); 14 | } 15 | -------------------------------------------------------------------------------- /src/helpers/getRocPackageDependencies.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets the Roc package dependencies from a `package.json`. 3 | * 4 | * @param {Object} packageJSON - A package.json file to fetch Roc package dependencies from. 5 | * 6 | * @returns {string[]} - An array with Roc packages that exists in the `package.json`. 7 | */ 8 | export default function getRocPackageDependencies(packageJSON) { 9 | return [ 10 | ...Object.keys(packageJSON.dependencies || {}).sort(), 11 | ...Object.keys(packageJSON.devDependencies || {}).sort(), 12 | ] 13 | .filter((dependency) => /^(?:@.*\/)?roc-package(-.+)/.test(dependency)); 14 | } 15 | -------------------------------------------------------------------------------- /src/cli/commands/helpers/isCommand.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject, isString, isFunction } from 'lodash'; 2 | 3 | // The property "command" is special and can not be used as a command 4 | export default function isCommand(commandsObject) { 5 | return (potentialCommmand) => 6 | potentialCommmand !== '__meta' && ( 7 | ( 8 | isString(commandsObject[potentialCommmand]) || 9 | isFunction(commandsObject[potentialCommmand]) 10 | ) || ( 11 | isPlainObject(commandsObject[potentialCommmand]) && 12 | commandsObject[potentialCommmand].command !== undefined 13 | )); 14 | } 15 | -------------------------------------------------------------------------------- /src/helpers/getPackageJSON.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import fileExists from './fileExists'; 4 | 5 | /** 6 | * Reads a `package.json` file. 7 | * 8 | * @param {string} [directory=process.cwd()] - In what directory to look for the `package.json`. 9 | * 10 | * @returns {Object|undefined} - The object in the `package.json` or undefined if it did not exists. 11 | */ 12 | export default function getPackageJSON(directory = process.cwd()) { 13 | if (fileExists('package.json', directory)) { 14 | return require(path.join(directory, 'package.json')); // eslint-disable-line 15 | } 16 | 17 | return undefined; 18 | } 19 | -------------------------------------------------------------------------------- /test/helpers/getPackageJSON.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import expect from 'expect'; 4 | 5 | import getPackageJSON from '../../src/helpers/getPackageJSON'; 6 | 7 | describe('helpers', () => { 8 | describe('getPackageJSON', () => { 9 | it('should correctly read package.json', () => { 10 | expect(getPackageJSON(path.join(__dirname, '../../')).name) 11 | .toBe('roc'); 12 | }); 13 | 14 | it('should correctly manage when no package.json exists', () => { 15 | expect(getPackageJSON(__dirname)) 16 | .toBe(undefined); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/documentation/helpers/getDefaultValue.js: -------------------------------------------------------------------------------- 1 | import { isRegExp } from 'lodash'; 2 | 3 | /** 4 | * Converts an object to a string. 5 | * 6 | * @param {Object} object - The object to convert to a string. 7 | * 8 | * @returns {string|null} - The converted object or null if the object is empty. 9 | */ 10 | export default function getDefaultValue(object) { 11 | if (object === undefined) { 12 | return undefined; 13 | } 14 | 15 | // Make sure we get something sensible when having a RegExp 16 | if (isRegExp(object)) { 17 | return object.toString(); 18 | } 19 | 20 | return JSON.stringify(object); 21 | } 22 | -------------------------------------------------------------------------------- /src/helpers/index.js: -------------------------------------------------------------------------------- 1 | export fileExists from './fileExists'; 2 | export folderExists from './folderExists'; 3 | export getAbsolutePath from './getAbsolutePath'; 4 | export getPackageJSON from './getPackageJSON'; 5 | export getRocPackageDependencies from './getRocPackageDependencies'; 6 | export getRocPluginDependencies from './getRocPluginDependencies'; 7 | export getSuggestions from './getSuggestions'; 8 | export keyboardDistance from './keyboardDistance'; 9 | export lazyFunctionRequire from './lazyFunctionRequire'; 10 | export merge from './merge'; 11 | export onProperty from './onProperty'; 12 | export validRocProject from './validRocProject'; 13 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/packages/b/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roc: { 3 | actions: [ 4 | () => () => () => { }, 5 | { 6 | extension: 'roc-package-core-dev', 7 | hook: 'before-clean', 8 | action: () => () => () => { }, 9 | post: () => () => () => { }, 10 | description: 'Some __description__.', 11 | }, 12 | ], 13 | config: { 14 | settings: { 15 | group: { 16 | value2: true, 17 | }, 18 | }, 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /test/converters/toInteger.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toInteger from '../../src/converters/toInteger'; 4 | 5 | describe('converters', () => { 6 | describe('toInteger', () => { 7 | it('should convert input to integer', () => { 8 | expect(toInteger(123)).toEqual(123); 9 | expect(toInteger('123')).toEqual(123); 10 | expect(toInteger('123.123')).toEqual(123); 11 | }); 12 | 13 | it('should convert input to undefined if unsupported', () => { 14 | expect(toInteger('asd')).toEqual(NaN); 15 | expect(toInteger(true)).toEqual(NaN); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/context/extensions/steps/runPostInits.js: -------------------------------------------------------------------------------- 1 | import processRocObject, { handleResult } from '../helpers/processRocObject'; 2 | 3 | export default function runPostInits(initialState) { 4 | return initialState.temp.postInits.reduceRight( 5 | (state, { postInit, name }) => 6 | processRocObject( 7 | handleResult({ name }, 8 | postInit({ 9 | context: state.context, 10 | localDependencies: state.dependencyContext.extensionsDependencies[name], 11 | }) 12 | ), state, false, false 13 | ), 14 | initialState 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/helpers/getAbsolutePath.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | /** 4 | * Makes a path absolute if not already is that. 5 | * 6 | * @param {string} filepath - The filepath to make absolute. 7 | * @param {string} [directory=process.cwd()] - The directory to resolve relative paths to. By default will use the 8 | * current working directory. 9 | * 10 | * @returns {string} - An absolute path. 11 | */ 12 | export default function getAbsolutePath(filepath, directory = process.cwd()) { 13 | if (filepath) { 14 | return path.isAbsolute(filepath) ? 15 | filepath : 16 | path.join(directory, filepath); 17 | } 18 | 19 | return undefined; 20 | } 21 | -------------------------------------------------------------------------------- /src/context/buildContext.js: -------------------------------------------------------------------------------- 1 | import { getAbsolutePath } from '../helpers'; 2 | 3 | import initContext from './initContext'; 4 | import validateAndUpdateSettings from './helpers/validateAndUpdateSettings'; 5 | 6 | /** 7 | * Builds the Roc configuration object without running the cli. 8 | */ 9 | export default function buildContext(dirPath, projectConfigPath, validate = true) { 10 | // Build the complete config object 11 | const context = initContext({ 12 | directory: getAbsolutePath(dirPath), 13 | projectConfigPath, 14 | verify: false, 15 | runtime: false, 16 | }); 17 | 18 | return validateAndUpdateSettings(context, validate); 19 | } 20 | -------------------------------------------------------------------------------- /test/converters/toArray.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toArray from '../../src/converters/toArray'; 4 | import toInteger from '../../src/converters/toInteger'; 5 | 6 | describe('converters', () => { 7 | describe('toArray', () => { 8 | it('should convert input to array without nested converter', () => { 9 | expect(toArray()(['1', '2', '3'])).toEqual(['1', '2', '3']); 10 | expect(toArray()('1,2,3')).toEqual(['1', '2', '3']); 11 | }); 12 | 13 | it('should convert input to array with nested converter', () => { 14 | expect(toArray(toInteger)('1,2,3')).toEqual([1, 2, 3]); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/context/extensions/helpers/updateExtensions.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject, union, get } from 'lodash'; 2 | 3 | // Updates __extensions for an object (current) by adding the values from old if it has the same keys 4 | export default function updateExtensions(current, old) { 5 | Object.keys(current).forEach((key) => { 6 | if (key === '__extensions') { 7 | // eslint-disable-next-line no-param-reassign 8 | current.__extensions = union(current.__extensions, get(old, '__extensions')); 9 | } else if (isPlainObject(current[`${key}`])) { 10 | updateExtensions(current[key], get(old, key)); 11 | } 12 | }); 13 | 14 | return current; 15 | } 16 | -------------------------------------------------------------------------------- /src/helpers/fileExists.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import getAbsolutePath from './getAbsolutePath'; 4 | 5 | /** 6 | * Verifies if a file exists. 7 | * 8 | * @param {string} filepath - The filepath to check. Will make it absolute if not already using {@link getAbsolutePath}. 9 | * @param {string} [directory] - The directory to base the filepath on. 10 | * 11 | * @returns {boolean} - Whether or not it is a file. 12 | */ 13 | export default function fileExists(filepath, directory) { 14 | const absoluteFilepath = getAbsolutePath(filepath, directory); 15 | try { 16 | return fs.statSync(absoluteFilepath).isFile(); 17 | } catch (error) { 18 | return false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/execute/helpers/getEnv.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import managePath from 'manage-path'; 4 | import { clone } from 'lodash'; 5 | 6 | import folderExists from '../../helpers/folderExists'; 7 | 8 | export default function getEnv(context) { 9 | const env = clone(process.env); 10 | if (context) { 11 | const alterPath = managePath(env); 12 | const nodeModulesBin = join(context, 'node_modules/.bin'); 13 | if (folderExists(nodeModulesBin)) { 14 | // We add this first, will make sure we match against what is in our context before "globals" 15 | alterPath.unshift(nodeModulesBin); 16 | } 17 | } 18 | 19 | return env; 20 | } 21 | -------------------------------------------------------------------------------- /src/helpers/folderExists.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import getAbsolutePath from './getAbsolutePath'; 4 | 5 | /** 6 | * Verifies if a folder exists. 7 | * 8 | * @param {string} filepath - The filepath to check. Will make it absolute if not already using {@link getAbsolutePath}. 9 | * @param {string} [directory] - The directory to base the filepath on. 10 | * 11 | * @returns {boolean} - Whether or not it is a folder. 12 | */ 13 | export default function folderExists(filepath, directory) { 14 | const absoluteFilepath = getAbsolutePath(filepath, directory); 15 | try { 16 | return fs.statSync(absoluteFilepath).isDirectory(); 17 | } catch (error) { 18 | return false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/init/generateTemplate/collect.js: -------------------------------------------------------------------------------- 1 | export default async function(data = {}, metadata, done) { 2 | const keys = Object.getOwnPropertyNames(data); 3 | 4 | for (const key of keys) { 5 | await unwrap(metadata, key, data[key]); 6 | } 7 | 8 | done(); 9 | } 10 | 11 | async function unwrap(metadata, key, dataProvider) { 12 | /* eslint-disable no-param-reassign */ 13 | if (typeof dataProvider === 'function') { 14 | try { 15 | metadata[key] = await dataProvider(metadata); 16 | } catch (e) { 17 | // error in async function, leave this key 18 | } 19 | return; 20 | } 21 | metadata[key] = dataProvider; 22 | /* eslint-enable */ 23 | } 24 | -------------------------------------------------------------------------------- /test/helpers/merge.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import merge from '../../src/helpers/merge'; 4 | 5 | describe('helpers', () => { 6 | describe('merge', () => { 7 | it('should call merge simple example correctly', () => { 8 | expect(merge({ 9 | a: 2, 10 | b: { 11 | f: 5, 12 | }, 13 | }, { 14 | a: 4, 15 | b: { 16 | g: 2, 17 | }, 18 | })).toEqual({ 19 | a: 4, 20 | b: { 21 | f: 5, 22 | g: 2, 23 | }, 24 | }); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["babel-plugin-istanbul"] 5 | } 6 | }, 7 | "plugins": [ 8 | "babel-plugin-transform-object-rest-spread", 9 | "babel-plugin-transform-es2015-spread", 10 | "babel-plugin-transform-es2015-function-name", 11 | "babel-plugin-transform-es2015-sticky-regex", 12 | "babel-plugin-transform-es2015-unicode-regex", 13 | "babel-plugin-transform-es2015-parameters", 14 | "babel-plugin-transform-es2015-destructuring", 15 | "babel-plugin-transform-es2015-modules-commonjs", 16 | "babel-plugin-transform-export-extensions", 17 | "babel-plugin-transform-async-to-generator" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/cli/commands/documentation/createTable.js: -------------------------------------------------------------------------------- 1 | import getDefaultOptions from '../getDefaultOptions'; 2 | import generateTable from '../../../documentation/generateTable'; 3 | 4 | export default function createTable(body, header, options, name, compact = true) { 5 | body.push({ 6 | name: options, 7 | level: 0, 8 | objects: getDefaultOptions(name), 9 | }); 10 | 11 | return generateTable(body, header, { 12 | compact, 13 | titleWrapper: (input) => `${input}:`, 14 | cellDivider: '', 15 | rowWrapper: (input) => ` ${input}`, 16 | cellWrapper: (input) => `${input} `, 17 | header: false, 18 | groupTitleWrapper: (input) => `${input}:`, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/init/generateTemplate/filter.js: -------------------------------------------------------------------------------- 1 | import match from 'micromatch'; 2 | 3 | import evaluate from './evaluate'; 4 | 5 | export default function filter(files, filters, data, done) { 6 | if (!filters) { 7 | return done(); 8 | } 9 | 10 | const fileNames = Object.keys(files); 11 | Object.keys(filters).forEach((glob) => { 12 | fileNames.forEach((file) => { 13 | if (match.isMatch(file, glob, { dot: true })) { 14 | const condition = filters[glob]; 15 | if (!evaluate(condition, data)) { 16 | delete files[file]; // eslint-disable-line 17 | } 18 | } 19 | }); 20 | }); 21 | 22 | return done(); 23 | } 24 | -------------------------------------------------------------------------------- /src/execute/executeSyncExit.js: -------------------------------------------------------------------------------- 1 | import executeSync from './executeSync'; 2 | 3 | /** 4 | * Will automatically close the node process if a command in executeSync had a non zero status code 5 | * without throwing an exception. 6 | * 7 | * This can be useful for when the command that is running is handling the error output itself. 8 | */ 9 | export default function executeSyncExit(command, options) { 10 | try { 11 | return executeSync(command, options); 12 | } catch (error) { 13 | // Only process if we got an error that have getCode 14 | if (!error.getExitCode) { 15 | throw error; 16 | } 17 | 18 | return process.exit(error.getExitCode()); // eslint-disable-line 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/validation/validators/isPromise.js: -------------------------------------------------------------------------------- 1 | import isProm from 'is-promise'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | 5 | /** 6 | * Validates a promise. 7 | * 8 | * @param {object} value - Something to validate. 9 | * @param {boolean} info - If type information should be returned. 10 | * @return {infoObject|boolean|string} - Type information or if it is valid. 11 | */ 12 | export default function isPromise(value, info) { 13 | if (info) { 14 | return createInfoObject({ 15 | validator: 'Promise', 16 | }); 17 | } 18 | 19 | if (value !== undefined && value !== null && !isProm(value)) { 20 | return 'Was not a Promise!'; 21 | } 22 | 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /src/validation/validators/isFunction.js: -------------------------------------------------------------------------------- 1 | import { isFunction as isFunctionLodash } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | 5 | /** 6 | * Validates an function. 7 | * 8 | * @param {object} value - Something to validate. 9 | * @param {boolean} info - If type information should be returned. 10 | * @return {infoObject|boolean|string} - Type information or if it is valid. 11 | */ 12 | export default function isFunction(value, info) { 13 | if (info) { 14 | return createInfoObject({ validator: 'Function' }); 15 | } 16 | 17 | if (value !== undefined && value !== null && !isFunctionLodash(value)) { 18 | return 'Was not a function!'; 19 | } 20 | 21 | return true; 22 | } 23 | -------------------------------------------------------------------------------- /test/commands/helpers/github.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import { getVersions } from '../../../src/commands/init/githubHelpers'; 4 | 5 | describe('commands', () => { 6 | describe('init', () => { 7 | describe('githubHelpers', () => { 8 | describe('getVersions', () => { 9 | it('should return a promise', () => { 10 | const result = getVersions('roc'); 11 | expect(result).toBeA(Promise); 12 | }); 13 | 14 | it('should throw if no package is given', () => { 15 | expect(() => getVersions()) 16 | .toThrow(); 17 | }); 18 | }); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/require/manageResolveRequest.js: -------------------------------------------------------------------------------- 1 | import createResolveRequest from './createResolveRequest'; 2 | 3 | /* Using global variables here to make sure that we can access the values set from different projects. 4 | * This guarantees that the variables will live outside the require cache, something that we need for stability. 5 | */ 6 | global.roc = global.roc || {}; 7 | global.roc.resolveRequest = global.roc.resolveRequest || undefined; 8 | 9 | export function setResolveRequest(exports, directory, dependencyContext) { 10 | global.roc.resolveRequest = createResolveRequest(exports, directory, dependencyContext); 11 | } 12 | 13 | export function getResolveRequest(identifier, async = false) { 14 | return global.roc.resolveRequest(identifier, async); 15 | } 16 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/ROC.md: -------------------------------------------------------------------------------- 1 | # complex 2 | 3 | This is automatically generated documentation for your Roc project. 4 | 5 | ## Extensions 6 | The extensions that are used in the project. 7 | 8 | ### Packages 9 | #### roc-package-a — [v1.0.0](https://www.npmjs.com/package/roc-package-a) 10 | 11 | 12 | ### Plugins 13 | #### roc-plugin-a — [v1.0.0](https://www.npmjs.com/package/roc-plugin-a) 14 | 15 | 16 | ## Documentation 17 | - [Actions](docs/Actions.md) 18 | - [Commands](docs/Commands.md) 19 | - [Configuration](docs/Configuration.md) 20 | - [Dependencies](docs/Dependencies.md) 21 | - [Hooks](docs/Hooks.md) 22 | - [Settings](docs/Settings.md) 23 | - [Extensions](docs/Extensions.md) 24 | 25 | --- 26 | _Generated by [Roc](https://github.com/rocjs/roc)_ 27 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/ROC.md: -------------------------------------------------------------------------------- 1 | # empty 2 | 3 | This is automatically generated documentation for your Roc project. 4 | 5 | ## Extensions 6 | The extensions that are used in the project. 7 | 8 | ### Packages 9 | #### roc-package-a — [v1.0.0](https://www.npmjs.com/package/roc-package-a) 10 | 11 | 12 | ### Plugins 13 | #### roc-plugin-a — [v1.0.0](https://www.npmjs.com/package/roc-plugin-a) 14 | 15 | 16 | ## Documentation 17 | - [Actions](docs/Actions.md) 18 | - [Commands](docs/Commands.md) 19 | - [Configuration](docs/Configuration.md) 20 | - [Dependencies](docs/Dependencies.md) 21 | - [Hooks](docs/Hooks.md) 22 | - [Settings](docs/Settings.md) 23 | - [Extensions](docs/Extensions.md) 24 | 25 | --- 26 | _Generated by [Roc](https://github.com/rocjs/roc)_ 27 | -------------------------------------------------------------------------------- /test/helpers/validRocProject.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import expect from 'expect'; 4 | 5 | import validRocProject from '../../src/helpers/validRocProject'; 6 | 7 | describe('helpers', () => { 8 | describe('validRocProject', () => { 9 | it('should validate a project with a roc field in package.json with at least one package', () => { 10 | expect(validRocProject(join(__dirname, 'fixtures', 'validRocProject', '1'))) 11 | .toBe(true); 12 | }); 13 | 14 | it('should validate a project that has at least one package in the dependencies', () => { 15 | expect(validRocProject(join(__dirname, 'fixtures', 'validRocProject', '2'))) 16 | .toBe(true); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | 4 | "parser": "babel-eslint", 5 | 6 | plugins: ["eslint-plugin-babel"], 7 | 8 | "rules": { 9 | "indent": [2, 4], 10 | "max-len": [2, 120, 4], 11 | "no-warning-comments": 1, 12 | "no-underscore-dangle": 0, 13 | "no-use-before-define": ["error", { "functions": false }], 14 | "prefer-template": [1], 15 | "consistent-return": [1], 16 | 17 | "generator-star-spacing": 0, 18 | "babel/generator-star-spacing": [2, { before: false, after: true }], 19 | 20 | "import/order": [2, { "groups": ["builtin", "external", "internal", "parent", "sibling", "index"], "newlines-between": "always"}], 21 | "import/newline-after-import": [2] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/converters/toArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Given an input the function will return an array. 3 | * 4 | * @param {function} converter - The converter to use for the elements in the array. 5 | * 6 | * @returns {function} - A converter that will convert the input to an array. 7 | */ 8 | export default function toArray(converter = (input) => input) { 9 | return (input) => { 10 | let parsed; 11 | try { 12 | parsed = JSON.parse(input); 13 | } catch (err) { 14 | // Ignore this case 15 | } 16 | 17 | if (Array.isArray(parsed)) { 18 | return parsed; 19 | } 20 | 21 | return input 22 | .toString() 23 | .split(',') 24 | .map((value) => converter(value.trim())); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /test/helpers/keyboardDistance.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import keyboardDistance from '../../src/helpers/keyboardDistance'; 4 | 5 | describe('helpers', () => { 6 | describe('keyboardDistance', () => { 7 | it('should throw error if first argument is not a single character', () => { 8 | expect(() => keyboardDistance('hello')) 9 | .toThrow(); 10 | }); 11 | 12 | it('should give back the same character if it´s one of the possible', () => { 13 | expect(keyboardDistance('g', ['a', 'f', 'd', 'r', 'g', 'v'])).toBe('g'); 14 | }); 15 | 16 | it('should give back the clostest one', () => { 17 | expect(keyboardDistance('g', ['a', 'f', 'd', 'e', 'w'])).toBe('f'); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/converters/convert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Given an input is a converter, it will return a converted result. 3 | * 4 | * @param {...function} converters - This is the converters. 5 | * 6 | * @returns {object} - The converted result. 7 | */ 8 | export default function convert(...converters) { 9 | if (!converters.length) { 10 | throw new Error('You need to use at least one converter.'); 11 | } 12 | 13 | return (input) => { 14 | for (const converter of converters) { 15 | const res = converter(input); 16 | 17 | // false and null is valid results 18 | if (!res && res !== false && res !== null) { 19 | continue; 20 | } 21 | 22 | return res; 23 | } 24 | 25 | return undefined; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /test/context/fixtures/defaultState.js: -------------------------------------------------------------------------------- 1 | export default { 2 | context: { 3 | actions: [], 4 | commands: {}, 5 | config: {}, 6 | dependencies: { 7 | exports: {}, 8 | uses: {}, 9 | requires: {}, 10 | }, 11 | directory: __dirname, 12 | extensionConfig: {}, 13 | hooks: {}, 14 | meta: {}, 15 | packageJSON: {}, 16 | usedExtensions: [], 17 | verbose: false, 18 | }, 19 | settings: { 20 | checkRequired: true, 21 | }, 22 | dependencyContext: { 23 | extensionsDependencies: {}, 24 | pathsToExtensions: {}, 25 | }, 26 | temp: { 27 | extensionsDevelopmentExports: {}, 28 | postInits: [], 29 | startTime: 1, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/validation/helpers/throwValidationError.js: -------------------------------------------------------------------------------- 1 | import { underline } from 'chalk'; 2 | 3 | /** 4 | * Throws error for failed validations. 5 | * 6 | * @param {string} name - String with the name of what failed the validation. 7 | * @param {string} message - Potential message from the validating function. 8 | * @param {object} value - The value that was provided. 9 | * @param {string} [type='field'] - What the failed validation value was. 10 | * @throws {Error} - Throws error if the configuration is invalid. 11 | */ 12 | export default function throwValidationError(name, message, value = '[Nothing]', type = 'field') { 13 | throw new Error( 14 | `Validation failed for ${type} ${underline(name)}\n` + 15 | `Received: ${JSON.stringify(value)}.` + 16 | ` ${message || ''}` 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/converters/automatic.js: -------------------------------------------------------------------------------- 1 | import { isBoolean, isObject, isRegExp } from 'lodash'; 2 | 3 | import { toArray, toRegExp, toBoolean, toInteger, toObject } from '../converters'; 4 | 5 | export default function automatic(value) { 6 | if (isBoolean(value)) { 7 | return (input) => toBoolean(input); 8 | } else if (isRegExp(value)) { 9 | return toRegExp; 10 | } else if (Array.isArray(value)) { 11 | // Take the first value in the array to decide what converter to use 12 | const converter = value.length > 0 ? 13 | automatic(value[0]) : undefined; 14 | return toArray(converter); 15 | } else if (Number.isInteger(value)) { 16 | return toInteger; 17 | } else if (isObject(value)) { 18 | return toObject; 19 | } 20 | 21 | return (input) => input; 22 | } 23 | -------------------------------------------------------------------------------- /src/validation/validateSettingsWrapper.js: -------------------------------------------------------------------------------- 1 | import log from '../log/default/large'; 2 | 3 | import validateSettings from './validateSettings'; 4 | 5 | /** 6 | * Validates the provided configuration object. 7 | */ 8 | export default function validateSettingsWrapper(settings, metaSettings = {}, toValidate = true) { 9 | try { 10 | if (toValidate === true) { 11 | validateSettings(settings, metaSettings); 12 | } else { 13 | toValidate.forEach((group) => { 14 | validateSettings(settings[group], metaSettings && metaSettings[group], false, `settings.${group}`); 15 | }); 16 | } 17 | } catch (err) { 18 | log.error( 19 | `Configuration was not valid.\n\n${err.message}`, 20 | 'Validation Problem' 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/validation/helpers/createInfoObject.js: -------------------------------------------------------------------------------- 1 | import { isFunction } from 'lodash'; 2 | 3 | export default function createInfoObject({ 4 | validator = () => ({ type: '' }), 5 | wrapper, 6 | required = false, 7 | canBeEmpty, 8 | converter, 9 | unmanagedObject = false, 10 | } = {}) { 11 | const info = isFunction(validator) ? validator(null, true) : { type: validator.toString(), canBeEmpty: null }; 12 | const type = wrapper ? wrapper(info.type) : info.type; 13 | const convert = converter ? converter(info.converter) : info.converter; 14 | return { 15 | type, 16 | canBeEmpty: canBeEmpty === undefined ? info.canBeEmpty : canBeEmpty, 17 | required: info.required || required, 18 | converter: convert, 19 | unmanagedObject: info.unmanagedObject || unmanagedObject, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/validation/validators/isRegExp.js: -------------------------------------------------------------------------------- 1 | import { isRegExp as isRegExpLodash } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import toRegExp from '../../converters/toRegExp'; 5 | 6 | /** 7 | * Validates a RegExp. 8 | * 9 | * @param {object} value - Something to validate. 10 | * @param {boolean} info - If type information should be returned. 11 | * @return {infoObject|boolean|string} - Type information or if it is valid. 12 | */ 13 | export default function isRegExp(value, info) { 14 | if (info) { 15 | return createInfoObject({ 16 | validator: 'RegExp', 17 | converter: () => toRegExp, 18 | }); 19 | } 20 | 21 | if (value !== undefined && value !== null && !isRegExpLodash(value)) { 22 | return 'Was not a RegExp!'; 23 | } 24 | 25 | return true; 26 | } 27 | -------------------------------------------------------------------------------- /test/documentation/helpers/getDefaultValue.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import getDefaultValue from '../../../src/documentation/helpers/getDefaultValue'; 4 | 5 | describe('documentation', () => { 6 | describe('helpers', () => { 7 | describe('getDefaultValue', () => { 8 | it('should return null for undefined', () => { 9 | expect(getDefaultValue(undefined)) 10 | .toBe(undefined); 11 | }); 12 | 13 | it('should return null as string', () => { 14 | expect(getDefaultValue(null)) 15 | .toBe('null'); 16 | }); 17 | 18 | it('should return RegExp as string', () => { 19 | expect(getDefaultValue(/abc/)) 20 | .toEqual('/abc/'); 21 | }); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/helpers/getAbsolutePath.js: -------------------------------------------------------------------------------- 1 | import { sep } from 'path'; 2 | 3 | import expect from 'expect'; 4 | 5 | import getAbsolutePath from '../../src/helpers/getAbsolutePath'; 6 | 7 | describe('helpers', () => { 8 | describe('getAbsolutePath', () => { 9 | it('should correctly append directory if not absolute', () => { 10 | expect(getAbsolutePath('roc.config.js', '/some/dir')) 11 | .toBe(`${sep}some${sep}dir${sep}roc.config.js`); 12 | }); 13 | 14 | it('should not touch an already absolute path', () => { 15 | expect(getAbsolutePath('/roc.config.js')) 16 | .toBe('/roc.config.js'); 17 | }); 18 | 19 | it('should return undefined if no path is given', () => { 20 | expect(getAbsolutePath()) 21 | .toBe(undefined); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/validation/validators/isBoolean.js: -------------------------------------------------------------------------------- 1 | import { isBoolean as isBooleanLodash } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import toBoolean from '../../converters/toBoolean'; 5 | 6 | /** 7 | * Validates an boolean. 8 | * 9 | * @param {object} value - Something to validate. 10 | * @param {boolean} info - If type information should be returned. 11 | * @return {infoObject|boolean|string} - Type information or if it is valid. 12 | */ 13 | export default function isBoolean(value, info) { 14 | if (info) { 15 | return createInfoObject({ 16 | validator: 'Boolean', 17 | converter: () => toBoolean, 18 | }); 19 | } 20 | 21 | if (value !== undefined && value !== null && !isBooleanLodash(value)) { 22 | return 'Was not a boolean!'; 23 | } 24 | 25 | return true; 26 | } 27 | -------------------------------------------------------------------------------- /src/validation/validators/isInteger.js: -------------------------------------------------------------------------------- 1 | import { isInteger as isIntegerLodash } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import toInteger from '../../converters/toInteger'; 5 | 6 | /** 7 | * Validates an integer. 8 | * 9 | * @param {object} value - Something to validate. 10 | * @param {boolean} info - If type information should be returned. 11 | * @return {infoObject|boolean|string} - Type information or if it is valid. 12 | */ 13 | export default function isInteger(value, info) { 14 | if (info) { 15 | return createInfoObject({ 16 | validator: 'Integer', 17 | converter: () => toInteger, 18 | }); 19 | } 20 | 21 | if (value !== undefined && value !== null && !isIntegerLodash(value)) { 22 | return 'Was not an integer!'; 23 | } 24 | 25 | return true; 26 | } 27 | -------------------------------------------------------------------------------- /src/validation/helpers/isValid.js: -------------------------------------------------------------------------------- 1 | import { isFunction, isRegExp } from 'lodash'; 2 | 3 | /** 4 | * Helper to use a validator. 5 | * 6 | * @param {object} value - Something to validate. 7 | * @param {function|RegExp} validator - A validator. 8 | * @return {boolean} - If valid or not. 9 | */ 10 | export default function isValid(value, validator) { 11 | // If we have no validator we assume the value to be valid 12 | if (!validator) { 13 | return true; 14 | } 15 | 16 | if (isFunction(validator)) { 17 | return validator(value); 18 | } else if (isRegExp(validator)) { 19 | if (!validator.test(value.toString())) { 20 | return `Did not match the regexp: ${validator}`; 21 | } 22 | 23 | return true; 24 | } 25 | 26 | throw new Error('Structure of configuration does not align with validation.'); 27 | } 28 | -------------------------------------------------------------------------------- /src/helpers/objectToArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Takes a object and transforms it into an array where the key is used as name property 3 | * If the input already is an array the input will be returned 4 | * 5 | * @example 6 | * { 7 | * list: { 8 | * description: "Hello World" 9 | * } 10 | * } 11 | * => 12 | * [{ 13 | * name: 'list', 14 | * description: "Hello World" 15 | * }] 16 | */ 17 | export default function objectToArray(obj = {}) { 18 | if (Array.isArray(obj)) { 19 | // In the future we should throw an exception here, 20 | // by keeping this here for now we will be able to feel out 21 | // this new structure and feel if it is the right fit 22 | return obj; 23 | } 24 | 25 | return Object.getOwnPropertyNames(obj) 26 | .map((name) => ({ 27 | ...obj[name], 28 | name, 29 | })); 30 | } 31 | -------------------------------------------------------------------------------- /src/execute/executeSync.js: -------------------------------------------------------------------------------- 1 | import spawnCommandSync from './helpers/spawnCommandSync'; 2 | import getEnv from './helpers/getEnv'; 3 | import getCommand from './helpers/getCommand'; 4 | import ExecuteError from './helpers/ExecuteError'; 5 | 6 | export default function executeSync(command, { context, cwd, args, silent } = {}) { 7 | const cmd = getCommand(command, args); 8 | const { status, stdout, stderr } = spawnCommandSync(cmd, { 9 | stdio: silent ? undefined : 'inherit', 10 | env: getEnv(context), 11 | cwd, 12 | }); 13 | 14 | if (status) { 15 | throw new ExecuteError(`The command "${cmd}" failed with error code ${status}`, 16 | cmd, 17 | status, 18 | stderr && stderr.toString(), 19 | stdout && stdout.toString() 20 | ); 21 | } 22 | 23 | return stdout && stdout.toString(); 24 | } 25 | -------------------------------------------------------------------------------- /src/validation/validators/isString.js: -------------------------------------------------------------------------------- 1 | import { isString as isStringLodash } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import toString from '../../converters/toString'; 5 | 6 | /** 7 | * Validates an string. 8 | * 9 | * @param {object} value - Something to validate. 10 | * @param {boolean} info - If type information should be returned. 11 | * @return {infoObject|boolean|string} - Type information or if it is valid. 12 | */ 13 | export default function isString(value, info) { 14 | if (info) { 15 | return createInfoObject({ 16 | validator: 'String', 17 | converter: () => toString, 18 | canBeEmpty: true, 19 | }); 20 | } 21 | 22 | if (value !== undefined && value !== null && !isStringLodash(value)) { 23 | return 'Was not a string!'; 24 | } 25 | 26 | return true; 27 | } 28 | -------------------------------------------------------------------------------- /src/validation/validators/isPath.js: -------------------------------------------------------------------------------- 1 | import { isString as isStringLodash } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import toString from '../../converters/toString'; 5 | 6 | /** 7 | * Validates an path. 8 | * 9 | * @param {object} value - Something to validate. 10 | * @param {boolean} info - If type information should be returned. 11 | * @return {infoObject|boolean|string} - Type information or if it is valid. 12 | */ 13 | export default function isPath(value, info) { 14 | if (info) { 15 | return createInfoObject({ 16 | validator: 'Filepath', 17 | converter: () => toString, 18 | canBeEmpty: true, 19 | }); 20 | } 21 | 22 | if (value !== undefined && value !== null && isStringLodash(value) !== true) { 23 | return 'Was not a filepath!'; 24 | } 25 | 26 | return true; 27 | } 28 | -------------------------------------------------------------------------------- /src/execute/helpers/spawnCommandSync.js: -------------------------------------------------------------------------------- 1 | import util from 'util'; 2 | import { spawnSync } from 'child_process'; 3 | 4 | /* 5 | * This is a sync variant of https://github.com/mmalecki/spawn-command 6 | * We will hopefully be able to replace this with a version from the package above. 7 | * See https://github.com/mmalecki/spawn-command/issues/6 for more details 8 | */ 9 | export default function spawnCommandSync(command, options) { 10 | let file; 11 | let args; 12 | 13 | if (process.platform === 'win32') { 14 | file = 'cmd.exe'; 15 | args = ['/s', '/c', '"' + command + '"']; 16 | options = util._extend({}, options); // eslint-disable-line 17 | options.windowsVerbatimArguments = true; // eslint-disable-line 18 | } else { 19 | file = '/bin/sh'; 20 | args = ['-c', command]; 21 | } 22 | 23 | return spawnSync(file, args, options); 24 | } 25 | -------------------------------------------------------------------------------- /src/helpers/validRocProject.js: -------------------------------------------------------------------------------- 1 | import { isObject } from 'lodash'; 2 | 3 | import getRocPackageDependencies from './getRocPackageDependencies'; 4 | import getPackageJSON from './getPackageJSON'; 5 | 6 | /** 7 | * Validates if a directory seems to be a Roc application project. 8 | * A valid Roc project should have a package.json file that contains some dependency that match 'roc-package-*' or 9 | * a `roc.config.js` file. 10 | * 11 | * @param {string} directory - The directory to validate. 12 | * 13 | * @returns {boolean} - Whether or not it is a valid Roc project. 14 | */ 15 | export default function validRocProject(directory) { 16 | const packageJSON = getPackageJSON(directory); 17 | 18 | return isObject(packageJSON) && ( 19 | (packageJSON.roc && packageJSON.roc.packages && packageJSON.roc.packages.length > 0) || 20 | getRocPackageDependencies(packageJSON).length > 0); 21 | } 22 | -------------------------------------------------------------------------------- /scripts/generate-documentation.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const commandsToMarkdown = require('../lib/documentation/markdown/commandsToMarkdown').default; 5 | const hooksToMarkdown = require('../lib/documentation/markdown/hooksToMarkdown').default; 6 | const getDefaults = require('../lib/context/helpers/getDefaults').default; 7 | const commands = require('../lib/commands').default; 8 | 9 | const context = getDefaults({ 10 | commands, 11 | }); 12 | 13 | // Write documentation for commands 14 | fs.writeFile( 15 | path.resolve('./docs/default/Commands.md'), 16 | commandsToMarkdown( 17 | 'roc', 18 | context.config, 19 | context.commands 20 | ) 21 | ); 22 | 23 | // Write documentation for hooks 24 | fs.writeFile( 25 | path.resolve('./docs/default/Hooks.md'), 26 | hooksToMarkdown( 27 | 'roc', 28 | context.hooks 29 | ) 30 | ); 31 | -------------------------------------------------------------------------------- /src/configuration/manageSettings.js: -------------------------------------------------------------------------------- 1 | import merge from '../helpers/merge'; 2 | 3 | import { getConfig, setConfig } from './manageConfig'; 4 | 5 | /** 6 | * Gets the settings in the configuration object. 7 | * 8 | * Will by default get all settings. 9 | */ 10 | export function getSettings(key) { 11 | const settings = getConfig().settings; 12 | return key ? settings[key] : settings; 13 | } 14 | 15 | /** 16 | * Appends settings to the configuration object. 17 | * 18 | * Will merge with the already existing settings object meaning that this function can be called multiple times and 19 | * the settings will be a merge of all those calls. 20 | */ 21 | export function appendSettings(settingsObject, customState) { 22 | return setConfig( 23 | merge( 24 | getConfig(customState), 25 | { settings: settingsObject } 26 | ), 27 | !customState 28 | ).settings; 29 | } 30 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | 3 | * [Introduction](/docs/Introduction.md) 4 | * Core 5 | * [CLI](/docs/CLI.md) 6 | * [Commands](/docs/Commands.md) 7 | * [Configuration](/docs/Configuration.md) 8 | * [Settings](/docs/Settings.md) 9 | * [Documentation Generation](/docs/DocumentationGeneration.md) 10 | * [Hooks & Actions](/docs/Hooks.md) 11 | * [Runtime](/docs/Runtime.md) 12 | * [Context](/docs/Context.md) 13 | * [Extension Loading](/docs/LoadingExtensions.md) 14 | * [Extensions](/docs/Extensions.md) 15 | * [Roc Object](/docs/RocObject.md) 16 | * [Projects](/docs/Projects.md) 17 | * [Templates](/docs/Templates.md) 18 | * [API](/docs/API.md) 19 | * [Converters](/docs/Converters.md) 20 | * [Validation](/docs/Validators.md) 21 | * [Logging](/docs/Logging.md) 22 | * Guides 23 | * [Create an extension](/docs/guides/CreateExtension.md) 24 | * [Create a template](/docs/guides/CreateTemplate.md) 25 | * [FAQ](/docs/FAQ.md) 26 | -------------------------------------------------------------------------------- /src/context/helpers/validateAndUpdateSettings.js: -------------------------------------------------------------------------------- 1 | import { appendSettings } from '../../configuration/manageSettings'; 2 | import addRaw from '../../configuration/addRaw'; 3 | import runHook from '../../hooks/runHook'; 4 | import validateSettingsWrapper from '../../validation/validateSettingsWrapper'; 5 | 6 | /* eslint-disable no-param-reassign */ 7 | export default function validateAndUpdateSettings(context, validate = false) { 8 | if (validate) { 9 | const config = context.config || {}; 10 | const meta = context.meta || {}; 11 | validateSettingsWrapper(config.settings, meta.settings); 12 | } 13 | 14 | // Add RAW configuration 15 | context.config = addRaw(context.config); 16 | 17 | runHook('roc')('update-settings', () => context.config.settings)( 18 | (newSettings) => { context.config.settings = appendSettings(newSettings, context.config); } 19 | ); 20 | 21 | return context; 22 | } 23 | /* eslint-enable */ 24 | -------------------------------------------------------------------------------- /src/validation/validators/required.js: -------------------------------------------------------------------------------- 1 | import createInfoObject from '../helpers/createInfoObject'; 2 | import isValid from '../helpers/isValid'; 3 | 4 | export const REQUIRED_ERROR = 'A value was required but none was given!'; 5 | 6 | /** 7 | * Marks that the value is required, that is that it's not undefined or null. 8 | * 9 | * @param {function} validator - Validator to validate against. 10 | * @return {function} - A function that takes a value and that returns true or false if valid or not. 11 | */ 12 | export default function required(validator) { 13 | return (input, info) => { 14 | if (info) { 15 | return createInfoObject({ validator, required: true }); 16 | } 17 | 18 | if (input === undefined || input === null) { 19 | return REQUIRED_ERROR; 20 | } 21 | 22 | if (!validator) { 23 | return true; 24 | } 25 | 26 | return isValid(input, validator); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /test/configuration/addRaw.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import addRaw from '../../src/configuration/addRaw'; 4 | 5 | describe('configuration', () => { 6 | describe('addRaw', () => { 7 | it('should update configuration with values from RAW and remove it', () => { 8 | const initialConfig = { 9 | settings: { 10 | group: { 11 | property: { 12 | a: 1, 13 | __raw: { 14 | a: 2, 15 | b: 3, 16 | }, 17 | }, 18 | }, 19 | }, 20 | }; 21 | const newConfig = addRaw(initialConfig); 22 | 23 | expect(newConfig.settings.group.property).toEqual({ 24 | a: 2, 25 | b: 3, 26 | }); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/converters/toBoolean.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toBoolean from '../../src/converters/toBoolean'; 4 | 5 | describe('converters', () => { 6 | describe('toBoolean', () => { 7 | it('should convert input to boolean', () => { 8 | expect(toBoolean(true)).toEqual(true); 9 | expect(toBoolean(false)).toEqual(false); 10 | 11 | expect(toBoolean('true')).toEqual(true); 12 | expect(toBoolean('false')).toEqual(false); 13 | }); 14 | 15 | it('should convert input to undefined if case does not match', () => { 16 | expect(toBoolean('True')).toEqual(undefined); 17 | expect(toBoolean('False')).toEqual(undefined); 18 | }); 19 | 20 | it('should convert input to undefined if unsupported', () => { 21 | expect(toBoolean('asd')).toEqual(undefined); 22 | expect(toBoolean(1)).toEqual(undefined); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | language: node_js 5 | node_js: 6 | - stable 7 | addons: 8 | code_climate: 9 | repo_token: 10 | secure: VAPgCV/BAOZyzWFvPdOO8pzD796+5M8BnGXFWPMwqSnhLPGZFJdUiCFnIRC6yV1tQTig05X5aYw8cYlpJZdTzp68vAae8x2Kd2PA4DG+fc06UTj+p5f8X5hCiMXIJGhV7adLm4rYIkxKgJKYn9yWcU3vvg61k46sTwBBohqRzdP8qS4jOR2ttNz38WLWY7jlQzOFRgcVxMaZbj6L7LIiUCJJqUxIoxy2WY9EPJ5tEjgiRr5xKHYnSbphl4qmSPHU0k1aa/d5zvO999Kxn1q+5O+NQc8OvsC9azE3blqlsCtIC2b0TuvwFoLpAtMecp7vPpICTYgBLq8mRBdWkHBlCrnavUYq0GD9JGUXFeOyeWnx8tfs3ILNpVrqHLl/mLOmVy2FJX47+64WQ6dB9HBmsYglFEHvnaQQ8I4ZUTvbwzN5li1yNHw145PsyGxfNyrNxNMWpsDrv51NQUPxt3YO2ipEhVggV92dL1MZyVWm0JYLZUCmIp0nUM9yC5cPrpQcQbuJJOkn3NxMc75pm7i3dk6+Vaa+vr+LuazvtD2P1Wkbh0m4cETbXwvuWUS7RsTQNWJTLj9NDknASpToTJYp28E/4eihiM1Fg1VE51iNOUpoRJtYKbXJ53ujCQoVdXJYufGFYAok7jCwOUQOWpJHNsp/WlSYhtx9psC6XH4YGxU= 11 | after_script: 12 | - codeclimate-test-reporter < coverage/lcov.info 13 | - cat coverage/lcov.info | node_modules/.bin/coveralls 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | getConfig, 3 | appendConfig, 4 | } from './configuration/manageConfig'; 5 | 6 | export { 7 | getSettings, 8 | appendSettings, 9 | } from './configuration/manageSettings'; 10 | 11 | export merge from './helpers/merge'; 12 | 13 | export runCli from './cli/runCli'; 14 | 15 | export execute from './execute'; 16 | export executeSync from './execute/executeSync'; 17 | export executeSyncExit from './execute/executeSyncExit'; 18 | 19 | export { getAbsolutePath, fileExists, folderExists, lazyFunctionRequire } from './helpers'; 20 | 21 | export runHook from './hooks/runHook'; 22 | 23 | export runHookDirectly from './hooks/runHookDirectly'; 24 | 25 | export { 26 | removeActions, 27 | } from './hooks/manageActions'; 28 | 29 | export { getResolveRequest } from './require/manageResolveRequest'; 30 | 31 | export generateDependencies from './require/utils/generateDependencies'; 32 | 33 | export initLog from './log'; 34 | 35 | export initRuntime from './runtime'; 36 | -------------------------------------------------------------------------------- /test/validation/validators/isPromise.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isPromise from '../../../src/validation/validators/isPromise'; 4 | 5 | describe('validators', () => { 6 | describe('isPromise', () => { 7 | it('should return info object if requested', () => { 8 | expect(isPromise(null, true)) 9 | .toEqual({ 10 | type: 'Promise', 11 | required: false, 12 | canBeEmpty: null, 13 | converter: undefined, 14 | unmanagedObject: false, 15 | }); 16 | }); 17 | 18 | it('should validate a Promise correctly', () => { 19 | expect(isPromise(Promise.resolve())) 20 | .toBe(true); 21 | }); 22 | 23 | it('should return error if value is not a Promise', () => { 24 | expect(isPromise(() => {})) 25 | .toInclude('not a Promise'); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/commands/init/githubHelpers.js: -------------------------------------------------------------------------------- 1 | import got from 'got'; 2 | 3 | export function getOfficialTemplates() { 4 | // TODO Implement pagination support before we reach over 100 repositories 5 | return got('https://api.github.com/users/rocjs/repos?per_page=100', { 6 | json: true, 7 | }) 8 | .then(response => response.body) 9 | .then(repos => repos.filter(({ name }) => name.indexOf('roc-template') !== -1)); 10 | } 11 | 12 | /** 13 | * Fetches an array of all the tags for a GitHub repo, used as possible versions for a template. 14 | * 15 | * @param {string} packageName - A package name, expected to match "username/repo" 16 | * 17 | * @returns {object[]} - Array of tags/versions for the package 18 | */ 19 | export function getVersions(packageName) { 20 | if (!packageName) { 21 | throw new Error('No packageName was given.'); 22 | } 23 | 24 | return got(`https://api.github.com/repos/${packageName}/tags`, { 25 | json: true, 26 | }) 27 | .then(response => response.body); 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/manageHooks.js: -------------------------------------------------------------------------------- 1 | // This needs to be global, same case as with configuration 2 | global.roc = global.roc || {}; 3 | global.roc.context = global.roc.context || {}; 4 | global.roc.context.hooks = global.roc.context.hooks || {}; 5 | 6 | /** 7 | * Register hooks with Roc. 8 | */ 9 | export function registerHooks(hooks, name, state = global.roc.context.hooks) { 10 | // eslint-disable-next-line 11 | state = { 12 | ...state, 13 | [name]: hooks, 14 | }; 15 | 16 | return state; 17 | } 18 | 19 | /** 20 | * Gets the registered hooks. 21 | * 22 | * @returns {Object} - The registered hooks as an object where the key will be the extension they belong to. 23 | */ 24 | export function getHooks() { 25 | return global.roc.context.hooks; 26 | } 27 | 28 | /** 29 | * Sets the registered hooks. 30 | * 31 | * @param {Object} hooks - The hooks as an object where the key will be the extension they belong to. 32 | */ 33 | export function setHooks(hooks) { 34 | global.roc.context.hooks = hooks; 35 | } 36 | -------------------------------------------------------------------------------- /test/validation/validators/isPath.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isPath from '../../../src/validation/validators/isPath'; 4 | import toString from '../../../src/converters/toString'; 5 | 6 | describe('validators', () => { 7 | describe('isPath', () => { 8 | it('should return info object if requested', () => { 9 | expect(isPath(null, true)) 10 | .toEqual({ 11 | type: 'Filepath', 12 | required: false, 13 | canBeEmpty: true, 14 | converter: toString, 15 | unmanagedObject: false, 16 | }); 17 | }); 18 | 19 | it('should validate a filepath correctly', () => { 20 | expect(isPath('/some/path')) 21 | .toBe(true); 22 | }); 23 | 24 | it('should return error if value is not a filepath', () => { 25 | expect(isPath(1)) 26 | .toInclude('not a filepath'); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/validation/validators/isRegExp.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isRegExp from '../../../src/validation/validators/isRegExp'; 4 | import toRegExp from '../../../src/converters/toRegExp'; 5 | 6 | describe('validators', () => { 7 | describe('isRegExp', () => { 8 | it('should return info object if requested', () => { 9 | expect(isRegExp(null, true)) 10 | .toEqual({ 11 | type: 'RegExp', 12 | required: false, 13 | canBeEmpty: null, 14 | converter: toRegExp, 15 | unmanagedObject: false, 16 | }); 17 | }); 18 | 19 | it('should validate a RegExp correctly', () => { 20 | expect(isRegExp(/abc/)) 21 | .toBe(true); 22 | }); 23 | 24 | it('should return error if value is not a RegExp', () => { 25 | expect(isRegExp('/abc/')) 26 | .toInclude('not a RegExp'); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /docs/default/Hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks for `roc` 2 | 3 | ## Hooks 4 | * [roc](#roc) 5 | * [update-settings](#update-settings) 6 | 7 | ## roc 8 | 9 | ### update-settings 10 | 11 | Expected to return new settings that should be merged with the existing ones. 12 | 13 | Makes it possible to modify the settings object before a command is started and after potential arguments from the command line and configuration file have been parsed. This is a good point to default to some value if no was given or modify something in the settings. 14 | 15 | __Initial value:__ _Nothing_ 16 | __Expected return value:__ `Object()` 17 | 18 | #### Arguments 19 | 20 | | Name | Description | Type | Required | Can be empty | 21 | | ----------- | ---------------------------------------------------------------------------- | ---------- | -------- | ------------ | 22 | | getSettings | A function that returns the settings after the context has been initialized. | `Function` | No | | 23 | -------------------------------------------------------------------------------- /test/hooks/manageHooks.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import { 4 | registerHooks, 5 | } from '../../src/hooks/manageHooks'; 6 | 7 | describe('hooks', () => { 8 | describe('manageHooks', () => { 9 | describe('registerHooks', () => { 10 | const testHooks = { 11 | a: {}, 12 | b: {}, 13 | }; 14 | 15 | it('should not mutate the state', () => { 16 | const originalState = {}; 17 | registerHooks(testHooks, 'roc-package-b', {}); 18 | 19 | expect(originalState) 20 | .toEqual({}); 21 | }); 22 | 23 | it('should register new hooks correctly', () => { 24 | const newState = registerHooks(testHooks, 'roc-package-b', {}); 25 | 26 | expect(Object.keys(newState).length) 27 | .toBe(1); 28 | expect(newState['roc-package-b']) 29 | .toEqual(testHooks); 30 | }); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/helpers/getRocPluginDependencies.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import getRocPluginDependencies from '../../src/helpers/getRocPluginDependencies'; 4 | 5 | describe('helpers', () => { 6 | describe('getRocPluginDependencies', () => { 7 | it('should correctly fetch all the Roc dependencies', () => { 8 | const packageJson = { 9 | dependencies: { 10 | roc: '^1.0.0', 11 | koa: '^2.0.0', 12 | colors: '*', 13 | 'roc-package-web': '^1.1.0', 14 | 'roc-plugin-web': '^1.1.0', 15 | '@name/roc-plugin-web-react': '~1.2.0', 16 | }, 17 | devDependencies: { 18 | mocha: '2.3.4', 19 | 'roc-plugin-test': '2.0.1', 20 | }, 21 | }; 22 | expect(getRocPluginDependencies(packageJson)) 23 | .toEqual(['@name/roc-plugin-web-react', 'roc-plugin-web', 'roc-plugin-test']); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/empty/docs/Hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks for `empty` 2 | 3 | ## Hooks 4 | * [roc](#roc) 5 | * [update-settings](#update-settings) 6 | 7 | ## roc 8 | 9 | ### update-settings 10 | 11 | Expected to return new settings that should be merged with the existing ones. 12 | 13 | Makes it possible to modify the settings object before a command is started and after potential arguments from the command line and configuration file have been parsed. This is a good point to default to some value if no was given or modify something in the settings. 14 | 15 | __Initial value:__ _Nothing_ 16 | __Expected return value:__ `Object()` 17 | 18 | #### Arguments 19 | 20 | | Name | Description | Type | Required | Can be empty | 21 | | ----------- | ---------------------------------------------------------------------------- | ---------- | -------- | ------------ | 22 | | getSettings | A function that returns the settings after the context has been initialized. | `Function` | No | | 23 | -------------------------------------------------------------------------------- /test/helpers/getRocPackageDependencies.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import getRocPackageDependencies from '../../src/helpers/getRocPackageDependencies'; 4 | 5 | describe('helpers', () => { 6 | describe('getRocPackageDependencies', () => { 7 | it('should correctly fetch all the Roc dependencies', () => { 8 | const packageJson = { 9 | dependencies: { 10 | roc: '^1.0.0', 11 | koa: '^2.0.0', 12 | colors: '*', 13 | 'roc-package-web': '^1.1.0', 14 | 'roc-plugin-web': '^1.1.0', 15 | '@name/roc-package-web-react': '~1.2.0', 16 | }, 17 | devDependencies: { 18 | mocha: '2.3.4', 19 | 'roc-package-test': '2.0.1', 20 | }, 21 | }; 22 | expect(getRocPackageDependencies(packageJson)) 23 | .toEqual(['@name/roc-package-web-react', 'roc-package-web', 'roc-package-test']); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/documentation/fixtures/projects/complex/docs/Hooks.md: -------------------------------------------------------------------------------- 1 | # Hooks for `complex` 2 | 3 | ## Hooks 4 | * [roc](#roc) 5 | * [update-settings](#update-settings) 6 | 7 | ## roc 8 | 9 | ### update-settings 10 | 11 | Expected to return new settings that should be merged with the existing ones. 12 | 13 | Makes it possible to modify the settings object before a command is started and after potential arguments from the command line and configuration file have been parsed. This is a good point to default to some value if no was given or modify something in the settings. 14 | 15 | __Initial value:__ _Nothing_ 16 | __Expected return value:__ `Object()` 17 | 18 | #### Arguments 19 | 20 | | Name | Description | Type | Required | Can be empty | 21 | | ----------- | ---------------------------------------------------------------------------- | ---------- | -------- | ------------ | 22 | | getSettings | A function that returns the settings after the context has been initialized. | `Function` | No | | 23 | -------------------------------------------------------------------------------- /test/helpers/getSuggestions.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import stripAnsi from 'strip-ansi'; 3 | 4 | import getSuggestions from '../../src/helpers/getSuggestions'; 5 | 6 | describe('helpers', () => { 7 | describe('getSuggestions', () => { 8 | it('should suggest the best alternative spelling', () => { 9 | const suggestion = getSuggestions(['te'], ['tea', 'test', 'testing']); 10 | expect(stripAnsi(suggestion)).toEqual('Did not understand te - Did you mean tea'); 11 | }); 12 | 13 | it('should inform when there is no possible alternative', () => { 14 | const suggestion = getSuggestions(['te'], ['testing']); 15 | expect(stripAnsi(suggestion)).toEqual('Did not understand te'); 16 | }); 17 | 18 | it('should add -- infront of suggestions if command is enabled', () => { 19 | const suggestion = getSuggestions(['te'], ['test', 'tea', 'testing'], '--'); 20 | expect(stripAnsi(suggestion)).toEqual('Did not understand --te - Did you mean --tea'); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/cli/commands/helpers/extractCommand.js: -------------------------------------------------------------------------------- 1 | import generateCommandsDocumentation from '../documentation/generateCommandsDocumentation'; 2 | 3 | import isCommandGroup from './isCommandGroup'; 4 | 5 | export default function extractCommand(commands = {}, potentialGroup, args, name, parents = []) { 6 | if (isCommandGroup(commands)(potentialGroup)) { 7 | const newGroupOrCommand = args.shift(); 8 | 9 | // return documentation string if neither group or command 10 | if (!newGroupOrCommand) { 11 | return generateCommandsDocumentation( 12 | commands[potentialGroup], 13 | name, 14 | parents.concat(potentialGroup) 15 | ); 16 | } 17 | 18 | // proceed extraction, register parent 19 | return extractCommand( 20 | commands[potentialGroup], 21 | newGroupOrCommand, 22 | args, 23 | name, 24 | parents.concat(potentialGroup) 25 | ); 26 | } 27 | 28 | return { 29 | commands, 30 | commandName: potentialGroup, 31 | parents, 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /src/configuration/addRaw.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject } from 'lodash'; 2 | 3 | export const RAW = '__raw'; 4 | 5 | export default function addRaw(config) { 6 | const manageSettings = (settingsSlice) => { 7 | let newSettings = {}; 8 | Object.keys(settingsSlice).forEach((key) => { 9 | if (isPlainObject(settingsSlice[key])) { 10 | if (key === RAW) { 11 | newSettings = { 12 | ...newSettings, 13 | ...settingsSlice[key], 14 | }; 15 | } else { 16 | newSettings = { 17 | ...newSettings, 18 | [key]: manageSettings(settingsSlice[key]), 19 | }; 20 | } 21 | } else { 22 | newSettings = { 23 | ...newSettings, 24 | [key]: settingsSlice[key], 25 | }; 26 | } 27 | }); 28 | return newSettings; 29 | }; 30 | 31 | return { 32 | ...config, 33 | settings: manageSettings(config.settings), 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2016 Verdens Gang AS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/execute/index.js: -------------------------------------------------------------------------------- 1 | import spawnCommand from 'spawn-command'; 2 | 3 | import getEnv from './helpers/getEnv'; 4 | import getCommand from './helpers/getCommand'; 5 | import ExecuteError from './helpers/ExecuteError'; 6 | 7 | /** 8 | * Executes a command string. 9 | * 10 | * Quite simple in its current state and should be expected to change in the future. 11 | */ 12 | export default function execute(command, { silent, cwd, context, args } = {}) { 13 | return new Promise((resolve, reject) => { 14 | const cmd = getCommand(command, args); 15 | const child = spawnCommand(cmd, { 16 | stdio: silent ? undefined : 'inherit', 17 | env: getEnv(context), 18 | cwd, 19 | }); 20 | 21 | child.on('exit', (exitCode) => { 22 | if (exitCode !== 0) { 23 | return reject( 24 | new ExecuteError(`The command "${cmd}" failed with error code ${exitCode}`, cmd, exitCode) 25 | ); 26 | } 27 | 28 | return resolve(exitCode); 29 | }); 30 | 31 | child.on('error', (error) => 32 | reject(error) 33 | ); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs. 2 | # Requires EditorConfig JetBrains Plugin - http://github.com/editorconfig/editorconfig-jetbrains 3 | 4 | # Set this file as the topmost .editorconfig 5 | # (multiple files can be used, and are applied starting from current document location) 6 | root = true 7 | 8 | [{package.json}] 9 | indent_style = space 10 | indent_size = 2 11 | 12 | # Use bracketed regexp to target specific file types or file locations 13 | [*.{js,json}] 14 | 15 | # Use hard or soft tabs ["tab", "space"] 16 | indent_style = space 17 | 18 | # Size of a single indent [an integer, "tab"] 19 | indent_size = tab 20 | 21 | # Number of columns representing a tab character [an integer] 22 | tab_width = 4 23 | 24 | # Line breaks representation ["lf", "cr", "crlf"] 25 | end_of_line = lf 26 | 27 | # ["latin1", "utf-8", "utf-16be", "utf-16le"] 28 | charset = utf-8 29 | 30 | # Remove any whitespace characters preceding newline characters ["true", "false"] 31 | trim_trailing_whitespace = true 32 | 33 | # Ensure file ends with a newline when saving ["true", "false"] 34 | insert_final_newline = true 35 | -------------------------------------------------------------------------------- /test/converters/toRegExp.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import toRegExp from '../../src/converters/toRegExp'; 4 | 5 | describe('converters', () => { 6 | describe('toRegExp', () => { 7 | it('should pass the value through if it already is a RegExp', () => { 8 | const regexp = /abc/; 9 | expect(toRegExp(regexp)).toEqual(regexp); 10 | }); 11 | 12 | it('should convert a string to regexp, without flags and slashes', () => { 13 | expect(toRegExp('abc')).toEqual(/abc/); 14 | }); 15 | 16 | it('should convert a string to regexp, without flags', () => { 17 | expect(toRegExp('/abc/')).toEqual(/abc/); 18 | }); 19 | 20 | it('should convert a string to regexp', () => { 21 | expect(toRegExp('/abc/g')).toEqual(/abc/g); 22 | }); 23 | 24 | it('should convert a string to regexp that only have a leading slash', () => { 25 | expect(toRegExp('/abc')).toEqual(/abc/); 26 | }); 27 | 28 | it('should convert a string to regexp that only have a trailing slash', () => { 29 | expect(toRegExp('abc/')).toEqual(/abc/); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /scripts/post-install.js: -------------------------------------------------------------------------------- 1 | const stat = require('fs').stat; 2 | 3 | stat('lib', (error, statResult) => { 4 | if (error || !statResult.isDirectory()) { 5 | console.warn( 6 | `${'-'.repeat(85)}\n` + 7 | 'Built output not found. It looks like you might be attempting to install Roc\n' + 8 | 'from GitHub. Roc sources need to be transpiled before use. We will now make a\n' + 9 | 'best-efforts attempt to transpile the code. This will only work if your development\n' + 10 | 'environment is set up appropriately, most importantly that babel is available.\n' + 11 | '-'.repeat(85) 12 | ); 13 | 14 | try { 15 | const execSync = require('child_process').execSync; // eslint-disable-line 16 | 17 | execSync('npm run build', { stdio: 'inherit' }); 18 | } catch (e) { 19 | console.error( 20 | `${'-'.repeat(85)}\n` + 21 | 'Failed to build Roc automatically. Please install Roc from\n' + 22 | 'npm, or clone the repo locally and build the library manually.\n' + 23 | '-'.repeat(85) 24 | ); 25 | throw e; 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/cli/commands/getMappings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates mappings between cli commands to their "path" in the configuration structure, their validator and type 3 | * converter. 4 | * 5 | * @param {Object} documentationObject - Documentation object to create mappings for. 6 | * 7 | * @returns {Object} - Properties are the cli command without leading dashes that maps to the mapped configuration. 8 | */ 9 | export default function getMappings(documentationObject = []) { 10 | const recursiveHelper = (groups) => { 11 | let mappings = {}; 12 | 13 | groups.forEach((group) => { 14 | group.objects.forEach((element) => { 15 | // Remove the two dashes in the beginning to match correctly 16 | mappings[element.cli.substr(2)] = { 17 | name: element.cli, 18 | path: element.path, 19 | converter: element.converter, 20 | validator: element.validator, 21 | }; 22 | }); 23 | 24 | mappings = Object.assign({}, mappings, recursiveHelper(group.children)); 25 | }); 26 | 27 | return mappings; 28 | }; 29 | 30 | return recursiveHelper(documentationObject); 31 | } 32 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const path = require('path'); 4 | 5 | const updateNotifier = require('update-notifier'); 6 | 7 | const packageJSON = require('../package.json'); 8 | const fileExists = require('../lib/helpers').fileExists; 9 | const getAbsolutePath = require('../lib/helpers').getAbsolutePath; 10 | const processArguments = require('../lib/cli/processArguments').default; 11 | 12 | const args = processArguments(); 13 | const directory = getAbsolutePath(args.coreOptions.directory || args.coreOptions.d || process.cwd()); 14 | 15 | const currentCli = require.resolve('./cli'); 16 | const localCli = path.resolve(directory, 'node_modules/roc/bin/cli'); 17 | 18 | if (currentCli !== localCli) { 19 | // Are we global? 20 | if (!fileExists(path.resolve(directory, 'node_modules/.bin/roc'))) { 21 | updateNotifier({ pkg: packageJSON }).notify({ defer: false }); 22 | require(currentCli); // eslint-disable-line 23 | } else { 24 | const cli = require.resolve(localCli); 25 | console.log('Found a local version of Roc, will use that over the global one.', '\n'); 26 | require(cli); // eslint-disable-line 27 | } 28 | } else { 29 | require(currentCli); // eslint-disable-line 30 | } 31 | -------------------------------------------------------------------------------- /src/cli/commands/getDefaultOptions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets and array with the default options for the cli. 3 | * Will be formatted to work with {@link generateTable} 4 | * 5 | * @param {string} name - What property the option/flag name should be set. 6 | * 7 | * @returns {Object[]} - Array with the default options formatted for {@link generateTable}. 8 | */ 9 | export default function getDefaultOptions(name) { 10 | return [{ 11 | [name]: '-b, --better-feedback', 12 | description: 'Enables source-map-support and loud-rejection.', 13 | }, { 14 | [name]: '-c, --config', 15 | description: 'Path to configuration file.', 16 | }, { 17 | [name]: '-d, --directory', 18 | description: 'Path to working directory.', 19 | }, { 20 | [name]: '-h, --help', 21 | description: 'Output usage information.', 22 | }, { 23 | [name]: '-V, --verbose', 24 | description: 'Enable verbose mode.', 25 | }, { 26 | [name]: '-v, --version', 27 | description: 'Output version number.', 28 | }]; 29 | } 30 | 31 | export const defaultOptions = ['help', 'config', 'verbose', 'directory', 'version', 'better-feedback']; 32 | export const defaultOptionsAlias = ['h', 'c', 'V', 'd', 'v', 'b']; 33 | -------------------------------------------------------------------------------- /test/validation/validators/isString.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isString from '../../../src/validation/validators/isString'; 4 | import toString from '../../../src/converters/toString'; 5 | 6 | describe('validators', () => { 7 | describe('isString', () => { 8 | it('should return info object if requested', () => { 9 | expect(isString(null, true)) 10 | .toEqual({ 11 | type: 'String', 12 | required: false, 13 | canBeEmpty: true, 14 | converter: toString, 15 | unmanagedObject: false, 16 | }); 17 | }); 18 | 19 | it('should validate a string correctly', () => { 20 | expect(isString('123')) 21 | .toBe(true); 22 | }); 23 | 24 | it('should return error if value is not a string', () => { 25 | expect(isString(123)) 26 | .toInclude('not a string'); 27 | }); 28 | 29 | it('should allow undefined and null', () => { 30 | expect(isString(null)) 31 | .toBe(true); 32 | 33 | expect(isString(undefined)) 34 | .toBe(true); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/cli/processArguments.js: -------------------------------------------------------------------------------- 1 | import minimist from 'minimist'; 2 | 3 | export default function parseArguments(argv = process.argv) { 4 | /* eslint-disable object-property-newline */ 5 | const { 6 | _, 7 | h, help, 8 | V, verbose, 9 | v, version, 10 | c, config, 11 | d, directory, 12 | b, 'better-feedback': betterFeedback, 13 | '--': extraArguments, 14 | ...extOptions, 15 | } = minimist(argv.slice(2), { '--': true }); 16 | 17 | // The first should be our command or commandgroup, if there is one 18 | const [groupOrCommand, ...argsWithoutOptions] = _; 19 | 20 | return { 21 | groupOrCommand, // commandgroup or command 22 | coreOptions: { // options managed and parsed by core 23 | h, help, 24 | V, verbose, 25 | v, version, 26 | c, config, 27 | d, directory, 28 | b, betterFeedback, 29 | }, 30 | extOptions, // options that will be forwarded to commands from context 31 | argsWithoutOptions, // remaining arguments with no associated options 32 | extraArguments, // arguments after the ended argument list (--) 33 | }; 34 | /* eslint-enable object-property-newline */ 35 | } 36 | -------------------------------------------------------------------------------- /docs/DocumentationGeneration.md: -------------------------------------------------------------------------------- 1 | # Documentation Generation 2 | An important part of Roc is documentation generation and is used both by extensions and projects. This utilizes the data structure that Roc implements to generate Markdown for several parts of Roc, mainly: 3 | 4 | * Overview (README.md / ROC.md) 5 | * Actions 6 | * Commands 7 | * Configuration 8 | * Dependencies 9 | * Extensions 10 | * Hooks 11 | * Settings 12 | 13 | ## Extensions 14 | Using `roc-internal-dev` extensions have a way to automatically generate documentation. 15 | 16 | ```bash 17 | $ rid docs 18 | ``` 19 | 20 | Will create a `README.md` for each extension along with a folder at `docs/` containing the rest of the documentation. 21 | 22 | ## Projects 23 | Projects can generate documentation for their current setup by using the command line interface. 24 | 25 | ```bash 26 | $ roc meta docs 27 | ``` 28 | 29 | This will create a `ROC.md` inside the root of the project along with several additional markdown files inside `/docs`. This is useful as a way to easily get an overview over the current state of the project. This command will only be available when inside a valid Roc project. 30 | 31 | [For more details about the command please see the generated documentation for it.](#/docs/default/Commands.md#docs) 32 | -------------------------------------------------------------------------------- /test/validation/validators/isInteger.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isInteger from '../../../src/validation/validators/isInteger'; 4 | import toInteger from '../../../src/converters/toInteger'; 5 | 6 | describe('validators', () => { 7 | describe('isInteger', () => { 8 | it('should return info object if requested', () => { 9 | expect(isInteger(null, true)) 10 | .toEqual({ 11 | type: 'Integer', 12 | required: false, 13 | canBeEmpty: null, 14 | converter: toInteger, 15 | unmanagedObject: false, 16 | }); 17 | }); 18 | 19 | it('should validate a integer correctly', () => { 20 | expect(isInteger(1)) 21 | .toBe(true); 22 | }); 23 | 24 | it('should return error if value is not a integer', () => { 25 | expect(isInteger('1')) 26 | .toInclude('not an integer'); 27 | }); 28 | 29 | it('should allow undefined and null', () => { 30 | expect(isInteger(null)) 31 | .toBe(true); 32 | 33 | expect(isInteger(undefined)) 34 | .toBe(true); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/documentation/markdown/configurationToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { isFunction, omit } from 'lodash'; 2 | 3 | export default function configurationToMarkdown(name, configuration, metaConfiguration, commandObject) { 4 | const config = omit(configuration, ['settings', 'project']); 5 | const configMeta = omit(metaConfiguration, 'settings'); 6 | const groups = Object.keys(config); 7 | 8 | const rows = []; 9 | 10 | rows.push(`# Config for \`${name}\``, ''); 11 | 12 | rows.push('Configuration that can be defined in `roc.config.js`, other than settings and project.', ''); 13 | 14 | if (groups.length === 0) { 15 | rows.push('__No config available.__', ''); 16 | return rows.join('\n'); 17 | } 18 | 19 | groups.forEach((group) => { 20 | rows.push(`## \`${group}\``); 21 | 22 | if (configMeta[group].description) { 23 | rows.push( 24 | isFunction(configMeta[group].description) ? 25 | configMeta[group].description(commandObject, config[group]) : 26 | configMeta[group].description 27 | , ''); 28 | } 29 | 30 | rows.push(`__Extensions__: ${configMeta[group].__extensions.join(', ')}`, ''); 31 | }); 32 | 33 | return rows.join('\n'); 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/lock.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { join } from 'path'; 3 | 4 | import log from '../log/default/small'; 5 | import getAlphaExtensionsThatAreNotLocked, { officialExtensions } 6 | from '../context/dependencies/getAlphaExtensionsThatAreNotLocked'; 7 | 8 | export default function freeze({ context: { packageJSON, directory } }) { 9 | const toUpdate = getAlphaExtensionsThatAreNotLocked(packageJSON); 10 | 11 | if (toUpdate) { 12 | const newPackageJSON = { ...packageJSON }; 13 | 14 | toUpdate.dependencies.forEach((dependency) => { 15 | console.log(dependency); 16 | newPackageJSON.dependencies[dependency] = officialExtensions[dependency]; 17 | }); 18 | 19 | toUpdate.devDependencies.forEach((dependency) => { 20 | console.log(dependency); 21 | newPackageJSON.devDependencies[dependency] = officialExtensions[dependency]; 22 | }); 23 | 24 | fs.writeFileSync(join(directory, 'package.json'), JSON.stringify(newPackageJSON, null, 2) + '\n'); 25 | 26 | log.success('Locked Roc dependencies in package.json'); 27 | log.log(' Remove node_modules and run npm install again to complete the process.'); 28 | } else { 29 | log.info('Seems like you already have locked the versions.'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/validation/helpers/isValid.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isValid from '../../../src/validation/helpers/isValid'; 4 | 5 | describe('roc', () => { 6 | describe('validation helpers', () => { 7 | describe('isValid', () => { 8 | it('should run the validator on the value if a function', () => { 9 | const spy = expect.createSpy(); 10 | isValid('value', spy); 11 | expect(spy).toHaveBeenCalledWith('value'); 12 | }); 13 | 14 | it('should return true if value match the RegExp', () => { 15 | expect(isValid('value', /\w/)) 16 | .toBe(true); 17 | }); 18 | 19 | it('should return error message if value does NOT match the RegExp', () => { 20 | expect(isValid('value', /\d/)) 21 | .toBeA('string'); 22 | }); 23 | 24 | it('should throw if validator is defined but not a function or RegExp', () => { 25 | expect(() => isValid('value', 2)) 26 | .toThrow(); 27 | }); 28 | 29 | it('should return true if validator is undefined', () => { 30 | expect(isValid('value')) 31 | .toBe(true); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/cli/commands/helpers/isCommandGroup.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isCommandGroup from '../../../../src/cli/commands/helpers/isCommandGroup'; 4 | 5 | describe('cli', () => { 6 | describe('commands', () => { 7 | describe('helpers', () => { 8 | describe('isCommandGroup', () => { 9 | const commandsObject = { 10 | meta: { 11 | docs: { 12 | command: () => {}, 13 | }, 14 | list: () => {}, 15 | extra: { 16 | test: () => {}, 17 | }, 18 | }, 19 | run: 'run', 20 | }; 21 | 22 | it('should correctly identify command groups', () => { 23 | expect(isCommandGroup(commandsObject)('run')).toBeFalsy(); 24 | expect(isCommandGroup(commandsObject)('meta')).toBeTruthy(); 25 | 26 | expect(isCommandGroup(commandsObject.meta)('docs')).toBeFalsy(); 27 | expect(isCommandGroup(commandsObject.meta)('list')).toBeFalsy(); 28 | expect(isCommandGroup(commandsObject.meta)('extra')).toBeTruthy(); 29 | }); 30 | }); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/cli/commands/helpers/isCommand.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isCommand from '../../../../src/cli/commands/helpers/isCommand'; 4 | 5 | describe('cli', () => { 6 | describe('commands', () => { 7 | describe('helpers', () => { 8 | describe('isCommand', () => { 9 | const commandsObject = { 10 | meta: { 11 | docs: { 12 | command: () => {}, 13 | }, 14 | list: () => {}, 15 | extra: { 16 | test: () => {}, 17 | }, 18 | }, 19 | run: 'run', 20 | }; 21 | 22 | it('should correctly identify commands', () => { 23 | expect(isCommand(commandsObject)('run')).toBeTruthy(); 24 | expect(isCommand(commandsObject)('meta')).toBeFalsy(); 25 | 26 | expect(isCommand(commandsObject.meta)('docs')).toBeTruthy(); 27 | expect(isCommand(commandsObject.meta)('list')).toBeTruthy(); 28 | expect(isCommand(commandsObject.meta)('extra')).toBeFalsy(); 29 | 30 | expect(isCommand(commandsObject.meta.extra)('test')).toBeTruthy(); 31 | }); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/documentation/markdown/createReadme.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import createReadme from '../../../src/documentation/markdown/createReadme'; 8 | import defaultCommands from '../../../src/commands'; 9 | import getContext from '../fixtures/getContext'; 10 | 11 | describe('documentation', () => { 12 | describe('markdown', () => { 13 | describe('createReadme', () => { 14 | it('should correctly format ROC.md for project empty', () => { 15 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 16 | const context = getContext(project, defaultCommands); 17 | 18 | expect(createReadme('empty', 'docs', false, { context })) 19 | .toEqual(lf(readFileSync(join(project, 'ROC.md'), 'utf8'))); 20 | }); 21 | 22 | it('should correctly format ROC.md for project complex', () => { 23 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 24 | const context = getContext(project, defaultCommands); 25 | 26 | expect(createReadme('complex', 'docs', false, { context })) 27 | .toEqual(lf(readFileSync(join(project, 'ROC.md'), 'utf8'))); 28 | }); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/helpers/lazyFunctionRequire.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import lazyFunctionRequire from '../../src/helpers/lazyFunctionRequire'; 4 | 5 | describe('helpers', () => { 6 | describe('lazyFunctionRequire', () => { 7 | beforeEach(() => { 8 | global.lazyFunctionRequire = undefined; 9 | }); 10 | 11 | afterEach(() => { 12 | global.lazyFunctionRequire = undefined; 13 | }); 14 | 15 | it('should manage CommonJS default exports', () => { 16 | delete require.cache[require.resolve('./fixtures/lazyFunctionRequire/1')]; 17 | 18 | const lazyFunction = lazyFunctionRequire(require)('./fixtures/lazyFunctionRequire/1'); 19 | expect(global.lazyFunctionRequire).toBe(undefined); 20 | expect(lazyFunction(1, 2, 3)).toEqual([1, 2, 3]); 21 | expect(global.lazyFunctionRequire).toBe(1); 22 | }); 23 | 24 | it('should manage ES module default exports', () => { 25 | delete require.cache[require.resolve('./fixtures/lazyFunctionRequire/2')]; 26 | 27 | const lazyFunction = lazyFunctionRequire(require)('./fixtures/lazyFunctionRequire/2'); 28 | expect(global.lazyFunctionRequire).toBe(undefined); 29 | expect(lazyFunction(1, 2, 3)).toEqual([1, 2, 3]); 30 | expect(global.lazyFunctionRequire).toBe(2); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/validation/validators/isFunction.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isFunction from '../../../src/validation/validators/isFunction'; 4 | 5 | describe('validators', () => { 6 | describe('toFunction', () => { 7 | it('should return infoObject if requested', () => { 8 | expect(isFunction(null, true)) 9 | .toEqual({ 10 | type: 'Function', 11 | required: false, 12 | canBeEmpty: null, 13 | converter: undefined, 14 | unmanagedObject: false, 15 | }); 16 | }); 17 | 18 | it('should validate a function correctly', () => { 19 | expect(isFunction(() => {})) 20 | .toBe(true); 21 | }); 22 | 23 | it('should validate a function correctly when undefined', () => { 24 | expect(isFunction(undefined)) 25 | .toBe(true); 26 | }); 27 | 28 | it('should return error if value is not a function', () => { 29 | expect(isFunction(1)) 30 | .toInclude('not a function'); 31 | }); 32 | 33 | it('should allow undefined and null', () => { 34 | expect(isFunction(null)) 35 | .toBe(true); 36 | 37 | expect(isFunction(undefined)) 38 | .toBe(true); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/context/dependencies/verifyInstalledProjectDependencies.js: -------------------------------------------------------------------------------- 1 | import { bold, dim, underline } from 'chalk'; 2 | 3 | import log from '../../log/default/large'; 4 | 5 | export default function verifyInstalledProjectDependencies({ dependencies, devDependencies }, exports = {}) { 6 | const matches = []; 7 | const allDependencies = { 8 | ...dependencies, 9 | ...devDependencies, 10 | }; 11 | 12 | Object.keys(exports).forEach((name) => { 13 | // If the same dependency is in the project we want to warn the user 14 | if (allDependencies[name]) { 15 | matches.push({ 16 | name, 17 | ...exports[name], 18 | }); 19 | } 20 | }); 21 | 22 | if (matches.length) { 23 | log.warn( 24 | `You have some dependencies in your package.json that also have been exported by extensions. This is probably a mistake. 25 | 26 | ${underline('Roc will prioritize the ones exported by the extensions.')} 27 | You can override this by adding "#" to the start of the require/import in the code, see documentation for more info. 28 | 29 | Dependencies that is both exported and in the projects package.json: 30 | ${matches.map((match) => `- ${bold(match.name)} ${dim('from')} ` + 31 | `${bold(match.extension)} ${dim('with version')} ${bold(match.version)}`).join('\n')}`, 32 | 'Dependencies' 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/validation/helpers/createInfoObject.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import createInfoObject from '../../../src/validation/helpers/createInfoObject'; 4 | 5 | describe('roc', () => { 6 | describe('validation helpers', () => { 7 | describe('createInfoObject', () => { 8 | it('should create a correct default object', () => { 9 | expect(createInfoObject()).toEqual({ 10 | type: '', 11 | canBeEmpty: undefined, 12 | required: false, 13 | converter: undefined, 14 | unmanagedObject: false, 15 | }); 16 | }); 17 | 18 | it('should create object with expected values when given a validator that provides info', () => { 19 | const stubConverter = () => {}; 20 | const info = { 21 | type: 'testtype', 22 | canBeEmpty: true, 23 | required: true, 24 | converter: stubConverter, 25 | unmanagedObject: false, 26 | }; 27 | 28 | const validatorProvidingInfo = () => info; 29 | 30 | expect( 31 | createInfoObject({ 32 | validator: validatorProvidingInfo, 33 | }) 34 | ).toEqual(info); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/validation/validators/isBoolean.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isBoolean from '../../../src/validation/validators/isBoolean'; 4 | import toBoolean from '../../../src/converters/toBoolean'; 5 | 6 | describe('validators', () => { 7 | describe('isBoolean', () => { 8 | it('should return infoObject if requested', () => { 9 | expect(isBoolean(null, true)) 10 | .toEqual({ 11 | type: 'Boolean', 12 | required: false, 13 | canBeEmpty: null, 14 | converter: toBoolean, 15 | unmanagedObject: false, 16 | }); 17 | }); 18 | 19 | it('should validate a boolean correctly', () => { 20 | expect(isBoolean(false)) 21 | .toBe(true); 22 | }); 23 | 24 | it('should validate a boolean correctly when undefined', () => { 25 | expect(isBoolean(undefined)) 26 | .toBe(true); 27 | }); 28 | 29 | it('should return error if value is not a boolean', () => { 30 | expect(isBoolean(1)) 31 | .toInclude('not a boolean'); 32 | }); 33 | 34 | it('should allow undefined and null', () => { 35 | expect(isBoolean(null)) 36 | .toBe(true); 37 | 38 | expect(isBoolean(undefined)) 39 | .toBe(true); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | export function consoleMockWrapper(cb) { 4 | const log = expect.spyOn(console, 'log'); 5 | const error = expect.spyOn(console, 'error'); 6 | 7 | const completed = (err) => { 8 | log.calls = []; 9 | log.restore(); 10 | 11 | error.calls = []; 12 | error.restore(); 13 | 14 | if (err) { 15 | throw err; 16 | } 17 | }; 18 | 19 | return Promise.resolve(cb(log, error)) 20 | .then(() => completed()) 21 | .catch(completed); 22 | } 23 | 24 | export function makeGetterSpy(obj, getter) { 25 | let callCount = 0; 26 | 27 | // store reference to old getter for restore 28 | const oldGetter = obj.__lookupGetter__(getter); 29 | 30 | if (!oldGetter) { 31 | throw new Error('Attempted to add getter spy to unsupported attribute'); 32 | } 33 | 34 | // define spy getter 35 | Object.defineProperty(obj, getter, { 36 | get() { 37 | callCount++; 38 | return oldGetter.call(obj); 39 | }, 40 | configurable: true, 41 | }); 42 | 43 | return { 44 | called() { 45 | return callCount > 0; 46 | }, 47 | callCount() { 48 | return callCount; 49 | }, 50 | restore() { 51 | Object.defineProperty(obj, getter, { 52 | get: oldGetter, 53 | configurable: true, 54 | }); 55 | }, 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/helpers/getSuggestions.js: -------------------------------------------------------------------------------- 1 | import leven from 'leven'; 2 | import chalk from 'chalk'; 3 | 4 | /** 5 | * Will create a string with suggestions for possible typos. 6 | * 7 | * @param {string[]} current - The current values that might be incorrect. 8 | * @param {string[]} possible - All the possible correct values. 9 | * @param {string} [prefix=''] - Something that the suggestion should be prefixed with. Useful for CLI options. 10 | * 11 | * @returns {string} - A string with possible suggestions for typos. 12 | */ 13 | export default function getSuggestions(current, possible, prefix = '') { 14 | const info = []; 15 | 16 | current.forEach((currentKey) => { 17 | let shortest = 0; 18 | let closest; 19 | 20 | for (const key of possible) { 21 | const distance = leven(currentKey, key); 22 | 23 | if (distance <= 0 || distance > 4) { 24 | continue; 25 | } 26 | 27 | if (shortest && distance >= shortest) { 28 | continue; 29 | } 30 | 31 | closest = key; 32 | shortest = distance; 33 | } 34 | 35 | if (closest) { 36 | info.push(`Did not understand ${chalk.underline(prefix + currentKey)}` + 37 | ` - Did you mean ${chalk.underline(prefix + closest)}`); 38 | } else { 39 | info.push(`Did not understand ${chalk.underline(prefix + currentKey)}`); 40 | } 41 | }); 42 | 43 | return info.join('\n'); 44 | } 45 | -------------------------------------------------------------------------------- /src/validation/validators/notEmpty.js: -------------------------------------------------------------------------------- 1 | import { isArrayLike, isPlainObject } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import isValid from '../helpers/isValid'; 5 | 6 | /** 7 | * Marks that the value can't be empty, undefined & null is allowed. 8 | * 9 | * Use together with {@link required} to also validate on undefined. 10 | * 11 | * @param {function} validator - Validator to validate against. 12 | * @return {function} - A function that takes a value and that returns true or false if valid or not. 13 | */ 14 | export default function notEmpty(validator) { 15 | function isEmpty(value) { 16 | if (isArrayLike(value)) { 17 | return !value.length; 18 | } 19 | 20 | if (isPlainObject(value)) { 21 | return !Object.keys(value).length; 22 | } 23 | 24 | // If it did not match one of the types above we will consider 25 | // it to not be empty 26 | return false; 27 | } 28 | 29 | return (input, info) => { 30 | if (info) { 31 | return createInfoObject({ 32 | validator, 33 | canBeEmpty: false, 34 | }); 35 | } 36 | 37 | if (input !== undefined && input !== null && isEmpty(input)) { 38 | return 'The value is required to not be empty!'; 39 | } 40 | 41 | if (!validator) { 42 | return true; 43 | } 44 | 45 | return isValid(input, validator); 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /test/helpers/onProperty.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import onProperty from '../../src/helpers/onProperty'; 4 | 5 | describe('helpers', () => { 6 | describe('onProperty', () => { 7 | it('should sort object on property', () => { 8 | const data = [{ 9 | a: 3, 10 | b: 'e', 11 | c: 1, 12 | }, { 13 | a: 1, 14 | b: 'e', 15 | c: 3, 16 | }]; 17 | 18 | expect([...data].sort(onProperty('a'))) 19 | .toEqual([{ 20 | a: 1, 21 | b: 'e', 22 | c: 3, 23 | }, { 24 | a: 3, 25 | b: 'e', 26 | c: 1, 27 | }]); 28 | 29 | expect([...data].sort(onProperty('b'))) 30 | .toEqual([{ 31 | a: 3, 32 | b: 'e', 33 | c: 1, 34 | }, { 35 | a: 1, 36 | b: 'e', 37 | c: 3, 38 | }]); 39 | 40 | expect([...data].sort(onProperty('c'))) 41 | .toEqual([{ 42 | a: 3, 43 | b: 'e', 44 | c: 1, 45 | }, { 46 | a: 1, 47 | b: 'e', 48 | c: 3, 49 | }]); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/documentation/markdown/hooksToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import hooksToMarkdown from '../../../src/documentation/markdown/hooksToMarkdown'; 8 | import getContext from '../fixtures/getContext'; 9 | 10 | describe('documentation', () => { 11 | describe('markdown', () => { 12 | describe('hooksToMarkdown', () => { 13 | it('should correctly format hooks when no hooks', () => { 14 | expect(hooksToMarkdown('name')) 15 | .toBe('# Hooks for `name`\n\n__No hooks available.__\n'); 16 | }); 17 | 18 | it('should correctly format hooks for project empty', () => { 19 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 20 | const context = getContext(project); 21 | 22 | expect(hooksToMarkdown('empty', context.hooks)) 23 | .toBe(lf(readFileSync(join(project, 'docs', 'Hooks.md'), 'utf8'))); 24 | }); 25 | 26 | it('should correctly format hooks for project complex', () => { 27 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 28 | const context = getContext(project); 29 | 30 | expect(hooksToMarkdown('complex', context.hooks)) 31 | .toBe(lf(readFileSync(join(project, 'docs', 'Hooks.md'), 'utf8'))); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/commands/init/generateTemplate/ask.js: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | 3 | import evaluate from './evaluate'; 4 | 5 | export default async function ask(prompts, data, done) { 6 | const keys = Object.getOwnPropertyNames(prompts); 7 | 8 | // Ask each question on order and wait for the response 9 | for (const key of keys) { 10 | await askQuestion(data, key, prompts[key]); 11 | } 12 | 13 | done(); 14 | } 15 | 16 | async function askQuestion(data, key, prompt) { 17 | // Skip prompts whose when condition is not met, will bind the current context into the evaluation 18 | if (prompt.when && !evaluate(prompt.when, data)) { 19 | return; 20 | } 21 | 22 | function withAnswers(entry) { 23 | if (typeof entry === 'function') { 24 | return entry(data); 25 | } 26 | return entry; 27 | } 28 | 29 | const answers = await inquirer.prompt([{ 30 | type: prompt.type, 31 | name: key, 32 | message: withAnswers(prompt.message) || key, 33 | default: withAnswers(prompt.default), 34 | choices: withAnswers(prompt.choices) || [], 35 | validate: prompt.validate || (() => true), 36 | filter: prompt.filter, 37 | }]); 38 | 39 | /* eslint-disable no-param-reassign */ 40 | if (Array.isArray(answers[key])) { 41 | data[key] = {}; 42 | answers[key].forEach((multiChoiceAnswer) => { data[key][multiChoiceAnswer] = true; }); 43 | } else { 44 | data[key] = answers[key]; 45 | } 46 | /* eslint-enable */ 47 | } 48 | 49 | -------------------------------------------------------------------------------- /test/documentation/markdown/actionsToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import actionsToMarkdown from '../../../src/documentation/markdown/actionsToMarkdown'; 8 | import getContext from '../fixtures/getContext'; 9 | 10 | describe('documentation', () => { 11 | describe('markdown', () => { 12 | describe('actionsToMarkdown', () => { 13 | it('should correctly format actions when no actions', () => { 14 | expect(actionsToMarkdown('name')) 15 | .toBe('# Actions for `name`\n\n__No actions available.__\n'); 16 | }); 17 | 18 | it('should correctly format actions for project empty', () => { 19 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 20 | const context = getContext(project); 21 | 22 | expect(actionsToMarkdown('empty', context.actions)) 23 | .toBe(lf(readFileSync(join(project, 'docs', 'Actions.md'), 'utf8'))); 24 | }); 25 | 26 | it('should correctly format actions for project complex', () => { 27 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 28 | const context = getContext(project); 29 | 30 | expect(actionsToMarkdown('complex', context.actions)) 31 | .toBe(lf(readFileSync(join(project, 'docs', 'Actions.md'), 'utf8'))); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/documentation/text/settingsToText.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import expect from 'expect'; 4 | import redent from 'redent'; 5 | import stripAnsi from 'strip-ansi'; 6 | import trimNewlines from 'trim-newlines'; 7 | 8 | import settingsToText from '../../../src/documentation/text/settingsToText'; 9 | import getContext from '../fixtures/getContext'; 10 | 11 | describe('documentation', () => { 12 | describe('text', () => { 13 | describe('settingsToText', () => { 14 | it('should return a message when there is no settings', () => { 15 | expect(settingsToText({}, {})) 16 | .toEqual('No settings available.'); 17 | }); 18 | 19 | it('should return a correct structured table', () => { 20 | const context = getContext(join(__dirname, '..', 'fixtures', 'projects', 'complex')); 21 | expect(stripAnsi(settingsToText(context.extensionConfig, context.meta))) 22 | .toEqual( 23 | redent(trimNewlines(` 24 | group 25 | 26 | | Description | Path | Default | CLI option | Required | Can be empty | 27 | | ----------- | ------------ | ------- | -------------- | -------- | ------------ | 28 | | | group.value2 | false | --group-value2 | No | Yes | 29 | ` 30 | )) 31 | ); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/require/verifyInstalledDependencies.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | import semver from 'semver'; 4 | 5 | import fileExists from '../helpers/fileExists'; 6 | 7 | /* 8 | A mismatch is one of the following: 9 | - Not in the package.json (One of the reasons for this is to avoid transitive dependencies) 10 | - Not the correct version installed 11 | */ 12 | export default function verifyInstalledDependencies(directory, dependencies = {}) { 13 | const projectJSON = require(join(directory, 'package.json')); // eslint-disable-line 14 | const mismatches = []; 15 | const allDependencies = { 16 | ...projectJSON.dependencies, 17 | ...projectJSON.devDependencies, 18 | }; 19 | Object.keys(dependencies).forEach((name) => { 20 | const requested = dependencies[name]; 21 | const current = allDependencies[name]; 22 | 23 | const packageJSON = join(directory, 'node_modules', name, 'package.json'); 24 | const installedVersion = fileExists(packageJSON) && require(packageJSON).version; // eslint-disable-line 25 | 26 | if ( 27 | !current || 28 | !semver.satisfies(installedVersion, requested.version) 29 | ) { 30 | mismatches.push({ 31 | name, 32 | current: installedVersion, 33 | requested: requested.version, 34 | extension: { 35 | name: requested.extension, 36 | path: requested.context, 37 | }, 38 | inPackageJSON: current, 39 | }); 40 | } 41 | }); 42 | return mismatches; 43 | } 44 | -------------------------------------------------------------------------------- /src/context/extensions/buildExtensionTree.js: -------------------------------------------------------------------------------- 1 | import getExtensions from './steps/getExtensions'; 2 | import processDevExports from './steps/processDevExports'; 3 | import processNormalExports from './steps/processNormalExports'; 4 | import runPostInits from './steps/runPostInits'; 5 | 6 | const log = require('debug')('roc:core:extensionBuilder'); 7 | 8 | /** 9 | * Builds the complete configuration objects. 10 | */ 11 | export default function buildExtensionTree(context, packages, plugins, checkRequired) { 12 | const completed = (state) => { 13 | const totalTime = process.hrtime(state.temp.startTime); 14 | log(`Completed loading extensions ${((totalTime[0] * 1000) + (totalTime[1] / 1000000)).toFixed(0)}ms`); 15 | return state; 16 | }; 17 | 18 | log('Loading extensions…'); 19 | return [ 20 | getExtensions('package')(packages), 21 | getExtensions('plugin')(plugins), 22 | processDevExports, 23 | processNormalExports, 24 | runPostInits, 25 | completed, 26 | ].reduce( 27 | (state, process) => process(state), 28 | // Initial state 29 | { 30 | context, 31 | 32 | settings: { 33 | checkRequired, 34 | }, 35 | 36 | temp: { 37 | postInits: [], 38 | extensionsDevelopmentExports: {}, 39 | extensionsNormalExports: {}, 40 | startTime: process.hrtime(), 41 | }, 42 | 43 | dependencyContext: { 44 | extensionsDependencies: {}, 45 | pathsToExtensions: {}, 46 | }, 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /test/documentation/markdown/dependenciesToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import dependenciesToMarkdown from '../../../src/documentation/markdown/dependenciesToMarkdown'; 8 | import getContext from '../fixtures/getContext'; 9 | 10 | describe('documentation', () => { 11 | describe('markdown', () => { 12 | describe('dependenciesToMarkdown', () => { 13 | it('should correctly format dependencies when no dependencies', () => { 14 | expect(dependenciesToMarkdown('name')) 15 | .toBe('# Dependencies for `name`\n\n__No dependencies available.__\n'); 16 | }); 17 | 18 | it('should correctly format dependencies for project empty', () => { 19 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 20 | const context = getContext(project); 21 | 22 | expect(dependenciesToMarkdown('empty', true, context.dependencies)) 23 | .toEqual(lf(readFileSync(join(project, 'docs', 'Dependencies.md'), 'utf8'))); 24 | }); 25 | 26 | it('should correctly format dependencies for project complex', () => { 27 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 28 | const context = getContext(project); 29 | 30 | expect(dependenciesToMarkdown('complex', true, context.dependencies)) 31 | .toEqual(lf(readFileSync(join(project, 'docs', 'Dependencies.md'), 'utf8'))); 32 | }); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/context/helpers/verifyConfigurationStructure.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject, difference, get } from 'lodash'; 2 | 3 | import { RAW } from '../../configuration/addRaw'; 4 | import getSuggestions from '../../helpers/getSuggestions'; 5 | import getUnmanagedObject from '../../helpers/getUnmanagedObject'; 6 | import log from '../../log/default/large'; 7 | 8 | export default function verifyConfigurationStructure(config, meta, projectConfig) { 9 | const getKeys = (obj, oldPath = '', allKeys = [], first = true) => { 10 | Object.keys(obj).forEach((key) => { 11 | const value = obj[key]; 12 | const newPath = oldPath + key; 13 | 14 | // We only want to check recursively if the key is settings or we already have 15 | // called the function recursively once 16 | const { validator } = get(meta, newPath, {}); 17 | 18 | if ( 19 | isPlainObject(value) && 20 | key !== RAW && 21 | (!first || key === 'settings') && 22 | getUnmanagedObject(validator) 23 | ) { 24 | getKeys(value, `${newPath}.`, allKeys, false); 25 | } else { 26 | allKeys.push(newPath); 27 | } 28 | }); 29 | 30 | return allKeys; 31 | }; 32 | const keys = getKeys(config); 33 | const diff = difference(getKeys(projectConfig), keys); 34 | 35 | if (diff.length > 0) { 36 | log.warn( 37 | `There was a mismatch in the project configuration structure, make sure this is correct.\n${ 38 | getSuggestions(diff, keys)}`, 39 | 'Configuration' 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/validation/validators/isArray.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isArray from '../../../src/validation/validators/isArray'; 4 | import toArray from '../../../src/converters/toArray'; 5 | 6 | describe('validators', () => { 7 | describe('isArray', () => { 8 | it('should return a wrapped validator', () => { 9 | expect(isArray()) 10 | .toBeA('function'); 11 | }); 12 | 13 | it('should return infoObject if requested', () => { 14 | const validator = () => ({ 15 | type: 'Type', 16 | required: false, 17 | }); 18 | 19 | expect(isArray(validator)(null, true)) 20 | .toEqual({ 21 | type: 'Array(Type)', 22 | required: false, 23 | canBeEmpty: true, 24 | converter: toArray(), 25 | unmanagedObject: false, 26 | }); 27 | }); 28 | 29 | it('should return error if value is not an array', () => { 30 | expect(isArray()('string')) 31 | .toInclude('not an array'); 32 | }); 33 | 34 | it('should validate an array correctly', () => { 35 | expect(isArray(() => true)([1])) 36 | .toBe(true); 37 | }); 38 | 39 | it('should validate if no validator is given and it is an array', () => { 40 | expect(isArray()([])) 41 | .toBe(true); 42 | }); 43 | 44 | it('should validate if value is undefined or null', () => { 45 | expect(isArray()(undefined)) 46 | .toBe(true); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/execute/index.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import proxyquire from 'proxyquire'; 3 | 4 | describe('execute', () => { 5 | let exitCode; 6 | let spawnError; 7 | const spawnSpyExit = expect.createSpy() 8 | .andReturn({ 9 | on: (name, cb) => { 10 | if (name === 'exit') { 11 | process.nextTick(() => cb(exitCode)); 12 | } else { 13 | process.nextTick(() => cb(spawnError)); 14 | } 15 | }, 16 | }); 17 | 18 | const execute = proxyquire('../../src/execute', { 19 | 'spawn-command': spawnSpyExit, 20 | }).default; 21 | 22 | describe('exit', () => { 23 | it('should reject the promise with an error and the correct exit code', () => { 24 | exitCode = 1; 25 | return execute('roc --help').catch((error) => { 26 | expect(error.getExitCode()).toBe(exitCode); 27 | expect(error.toString()).toInclude('The command "roc --help" failed with error code 1'); 28 | }); 29 | }); 30 | 31 | it('should resolve the promise with the correct exit code', () => { 32 | exitCode = 0; 33 | return execute('roc --help').then((code) => { 34 | expect(code).toBe(exitCode); 35 | }); 36 | }); 37 | }); 38 | 39 | describe('error', () => { 40 | it('should reject the promise with the same error as received', () => { 41 | spawnError = new Error('a error happened'); 42 | return execute('roc --help').catch((error) => { 43 | expect(error).toBe(spawnError); 44 | }); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/documentation/markdown/settingsToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import settingsToMarkdown from '../../../src/documentation/markdown/settingsToMarkdown'; 8 | import defaultCommands from '../../../src/commands'; 9 | import getContext from '../fixtures/getContext'; 10 | 11 | describe('documentation', () => { 12 | describe('markdown', () => { 13 | describe('settingsToMarkdown', () => { 14 | it('should correctly format settings when no settings', () => { 15 | expect(settingsToMarkdown('name')) 16 | .toBe('# Settings for `name`\n\n__No settings available.__\n'); 17 | }); 18 | 19 | it('should correctly format settings for project empty', () => { 20 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 21 | const context = getContext(project, defaultCommands); 22 | 23 | expect(settingsToMarkdown('empty', context.extensionConfig, context.meta)) 24 | .toEqual(lf(readFileSync(join(project, 'docs', 'Settings.md'), 'utf8'))); 25 | }); 26 | 27 | it('should correctly format settings for project complex', () => { 28 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 29 | const context = getContext(project, defaultCommands); 30 | 31 | expect(settingsToMarkdown('complex', context.extensionConfig, context.meta)) 32 | .toEqual(lf(readFileSync(join(project, 'docs', 'Settings.md'), 'utf8'))); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/context/extensions/steps/runPostInits.js: -------------------------------------------------------------------------------- 1 | import expect, { createSpy } from 'expect'; 2 | 3 | import runPostInits from '../../../../src/context/extensions/steps/runPostInits'; 4 | import defaultState from '../../fixtures/defaultState'; 5 | 6 | describe('context', () => { 7 | describe('extensions', () => { 8 | describe('steps', () => { 9 | describe('runPostInits', () => { 10 | it('should return the inital state if no postInit has been registered', () => { 11 | expect(runPostInits({ ...defaultState })).toEqual(defaultState); 12 | }); 13 | 14 | it('should call registered postInit in the right order and return back the correct state', () => { 15 | const order = []; 16 | const mock1 = createSpy().andCall(() => { order.push(1); }); 17 | const mock2 = createSpy().andCall(() => { order.push(2); }); 18 | const mock3 = createSpy().andCall(() => { order.push(3); }); 19 | 20 | const state = { 21 | ...defaultState, 22 | temp: { 23 | ...defaultState.temp, 24 | postInits: [ 25 | { postInit: mock1, name: 'a' }, 26 | { postInit: mock2, name: 'b' }, 27 | { postInit: mock3, name: 'c' }, 28 | ], 29 | }, 30 | }; 31 | 32 | expect(runPostInits(state)).toEqual(state); 33 | expect(order).toEqual([3, 2, 1]); 34 | }); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/documentation/markdown/commandsToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import commandsToMarkdown from '../../../src/documentation/markdown/commandsToMarkdown'; 8 | import defaultCommands from '../../../src/commands'; 9 | import getContext from '../fixtures/getContext'; 10 | 11 | describe('documentation', () => { 12 | describe('markdown', () => { 13 | describe('commandsToMarkdown', () => { 14 | it('should correctly format commands when no commands', () => { 15 | expect(commandsToMarkdown('name')) 16 | .toBe('# Commands for `name`\n\n__No commands available.__\n'); 17 | }); 18 | 19 | it('should correctly format commands for project empty', () => { 20 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 21 | const context = getContext(project, defaultCommands); 22 | 23 | expect(commandsToMarkdown('empty', context.extensionConfig, context.commands, '/docs/Settings.md')) 24 | .toEqual(lf(readFileSync(join(project, 'docs', 'Commands.md'), 'utf8'))); 25 | }); 26 | 27 | it('should correctly format commands for project complex', () => { 28 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 29 | const context = getContext(project, defaultCommands); 30 | 31 | expect(commandsToMarkdown('complex', context.extensionConfig, context.commands, '/docs/Settings.md')) 32 | .toEqual(lf(readFileSync(join(project, 'docs', 'Commands.md'), 'utf8'))); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/documentation/markdown/configurationToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import configurationToMarkdown from '../../../src/documentation/markdown/configurationToMarkdown'; 8 | import getContext from '../fixtures/getContext'; 9 | 10 | describe('documentation', () => { 11 | describe('markdown', () => { 12 | describe('configurationToMarkdown', () => { 13 | it('should correctly format config when no config', () => { 14 | expect(configurationToMarkdown('name')) 15 | // eslint-disable-next-line 16 | .toBe('# Config for `name`\n\nConfiguration that can be defined in `roc.config.js`, other than settings and project.\n\n__No config available.__\n'); 17 | }); 18 | 19 | it('should correctly format config for project empty', () => { 20 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 21 | const context = getContext(project); 22 | 23 | expect(configurationToMarkdown('empty', context.extensionConfig, context.meta)) 24 | .toBe(lf(readFileSync(join(project, 'docs', 'Configuration.md'), 'utf8'))); 25 | }); 26 | 27 | it('should correctly format config for project complex', () => { 28 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 29 | const context = getContext(project); 30 | 31 | expect(configurationToMarkdown('complex', context.extensionConfig, context.meta)) 32 | .toBe(lf(readFileSync(join(project, 'docs', 'Configuration.md'), 'utf8'))); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/context/dependencies/verifyRequiredDependencies.js: -------------------------------------------------------------------------------- 1 | import { green, red } from 'chalk'; 2 | 3 | import log from '../../log/default/large'; 4 | import verifyInstalledDependencies from '../../require/verifyInstalledDependencies'; 5 | import generateTable from '../../documentation/generateTable'; 6 | 7 | export default function verifyRequiredDependencies(directory, required) { 8 | const mismatches = verifyInstalledDependencies(directory, required); 9 | if (mismatches.length > 0) { 10 | const header = { 11 | name: { 12 | name: 'Dependency', 13 | }, 14 | current: { 15 | name: 'Installed version', 16 | renderer: (input) => { 17 | if (input) { 18 | return input; 19 | } 20 | 21 | return red('Not installed'); 22 | }, 23 | }, 24 | requested: { 25 | name: 'Requested version', 26 | }, 27 | extension: { 28 | name: 'Extension', 29 | renderer: (input) => input.name, 30 | }, 31 | inPackageJSON: { 32 | name: 'In package.json', 33 | renderer: (input) => { 34 | if (input) { 35 | return green('Yes') + ` - ${input}`; 36 | } 37 | 38 | return red('No'); 39 | }, 40 | }, 41 | }; 42 | 43 | const body = [{ 44 | objects: mismatches, 45 | name: 'Some required dependencies was not found!\n', 46 | level: 0, 47 | }]; 48 | 49 | log.error( 50 | generateTable(body, header), 51 | 'Missing dependencies' 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/documentation/markdown/extensionsToMarkdown.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readFileSync } from 'fs'; 3 | 4 | import expect from 'expect'; 5 | import { lf } from 'eol'; 6 | 7 | import extensionsToMarkdown from '../../../src/documentation/markdown/extensionsToMarkdown'; 8 | import getContext from '../fixtures/getContext'; 9 | 10 | describe('documentation', () => { 11 | describe('markdown', () => { 12 | describe('extensionsToMarkdown', () => { 13 | it('should correctly format extensions when no extensions', () => { 14 | expect(extensionsToMarkdown('name')) 15 | // eslint-disable-next-line 16 | .toBe('# Extensions for `name`\n\nThe extensions that are used in the project, indirect and direct, in the order that they were added.\n\n## Packages\n_No packages._\n\n## Plugins\n_No plugins._\n'); 17 | }); 18 | 19 | it('should correctly format extensions for project empty', () => { 20 | const project = join(__dirname, '..', 'fixtures', 'projects', 'empty'); 21 | const context = getContext(project); 22 | 23 | expect(extensionsToMarkdown('empty', context.usedExtensions, { context })) 24 | .toBe(lf(readFileSync(join(project, 'docs', 'Extensions.md'), 'utf8'))); 25 | }); 26 | 27 | it('should correctly format extensions for project complex', () => { 28 | const project = join(__dirname, '..', 'fixtures', 'projects', 'complex'); 29 | const context = getContext(project); 30 | 31 | expect(extensionsToMarkdown('complex', context.usedExtensions, { context })) 32 | .toBe(lf(readFileSync(join(project, 'docs', 'Extensions.md'), 'utf8'))); 33 | }); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/log/initLogSmall.js: -------------------------------------------------------------------------------- 1 | import { isBoolean } from 'lodash'; 2 | import chalk from 'chalk'; 3 | import symbols from 'log-symbols'; 4 | 5 | import { getContext } from '../context/helpers/manageContext'; 6 | 7 | export default function initLogSmall() { 8 | return { 9 | log: logger('log'), 10 | info: logger('info', 'cyan', symbols.info), 11 | warn: logger('warn', 'yellow', symbols.warning), 12 | error: logger('error', 'red', symbols.error), 13 | success: logger('log', 'green', symbols.success), 14 | raw: logger, 15 | }; 16 | } 17 | 18 | function logger(level, color, symbol) { 19 | const validLevels = ['info', 'warn', 'error', 'log']; 20 | if (validLevels.indexOf(level) === -1) { 21 | throw new Error(`The provided level "${level}" is not valid, try one of: ${validLevels.join(', ')}`); 22 | } 23 | return (...args) => { 24 | const message = args[0] || ''; 25 | const error = isBoolean(args[1]) ? args[2] : args[1]; 26 | const showSymbol = (isBoolean(args[1]) ? args[1] : args[2]) || true; 27 | 28 | const symbolText = symbol && showSymbol ? `${symbol} ` : ''; 29 | const log = console[level]; // eslint-disable-line 30 | 31 | log(!symbolText && color ? chalk[color](symbolText + message) : symbolText + message); 32 | printError(error, log); 33 | 34 | if (level === 'error') { 35 | process.exit(process.exitCode || 1); // eslint-disable-line 36 | } 37 | }; 38 | } 39 | 40 | function printError(error, log) { 41 | if (error && error.message) { 42 | // Transform Error: XYZ to XYZ for a nicer error message 43 | // We will only change the error if it is a generic error 44 | log(`\n${(getContext().verbose ? error.stack : error.toString().replace(/^Error: /, ''))}`); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/validation/validators/isArray.js: -------------------------------------------------------------------------------- 1 | import { isArray as isArrayLodash, isPlainObject } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import isValid from '../helpers/isValid'; 5 | import toArray from '../../converters/toArray'; 6 | 7 | /** 8 | * Validates an array using a validator. 9 | * 10 | * @param {function|RegExp} validator - The validator to use on the elements in the array. 11 | * @return {function} - A function that takes a value and that returns true or false if valid or not. 12 | */ 13 | export default function isArray(validator) { 14 | return (input, info) => { 15 | if (info) { 16 | return createInfoObject({ 17 | validator, 18 | converter: (converter) => toArray(converter), 19 | wrapper: (wrap) => `Array(${wrap})`, 20 | canBeEmpty: true, 21 | }); 22 | } 23 | 24 | if (input === undefined || input === null) { 25 | return true; 26 | } 27 | 28 | if (!isArrayLodash(input)) { 29 | return 'Was not an array!'; 30 | } 31 | 32 | for (const index in input) { // eslint-disable-line 33 | const result = isValid(input[index], validator); 34 | if (result !== true) { 35 | if (isPlainObject(result)) { 36 | return { 37 | key: `${result.key}[${index}]`, 38 | value: result.value, 39 | message: result.message, 40 | }; 41 | } 42 | return { 43 | key: `[${index}]`, 44 | value: input[index], 45 | message: result, 46 | }; 47 | } 48 | } 49 | 50 | return true; 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /src/log/helpers/labels.js: -------------------------------------------------------------------------------- 1 | import { 2 | white, black, red, yellow, green, cyan, 3 | bgRed, bgYellow, bgGreen, bgCyan, 4 | } from 'chalk'; 5 | 6 | /** 7 | * Formats a string suitable for error labels. 8 | * 9 | * @param {string} label - The label to format for error labels. 10 | * @param {string} text - The text to format for error labels. 11 | * 12 | * @returns {string} - The formatted text. 13 | */ 14 | export function error(label, text) { 15 | const title = text ? ` ${red(text)}` : ''; 16 | return bgRed(white(` ${label} `)) + title; 17 | } 18 | 19 | /** 20 | * Formats a string suitable for warning labels. 21 | * 22 | * @param {string} label - The label to format for warning labels. 23 | * @param {string} text - The text to format for warning labels. 24 | * 25 | * @returns {string} - The formatted text. 26 | */ 27 | export function warn(label, text) { 28 | const title = text ? ` ${yellow(text)}` : ''; 29 | return bgYellow(black(` ${label} `)) + title; 30 | } 31 | 32 | /** 33 | * Formats a string suitable for confirmation labels. 34 | * 35 | * @param {string} label - The label to format for confirmation labels. 36 | * @param {string} text - The text to format for confirmation labels. 37 | * 38 | * @returns {string} - The formatted text. 39 | */ 40 | export function log(label, text) { 41 | const title = text ? ` ${green(text)}` : ''; 42 | return bgGreen(white(` ${label} `)) + title; 43 | } 44 | 45 | /** 46 | * Formats a string suitable for info labels. 47 | * 48 | * @param {string} label - The label to format for info labels. 49 | * @param {string} text - The text to format for info labels. 50 | * 51 | * @returns {string} - The formatted text. 52 | */ 53 | export function info(label, text) { 54 | const title = text ? ` ${cyan(text)}` : ''; 55 | return bgCyan(black(` ${label} `)) + title; 56 | } 57 | -------------------------------------------------------------------------------- /src/validation/validators/oneOf.js: -------------------------------------------------------------------------------- 1 | import convert from '../../converters/convert'; 2 | import createInfoObject from '../helpers/createInfoObject'; 3 | import getInfoObject from '../helpers/getInfoObject'; 4 | import isValid from '../helpers/isValid'; 5 | 6 | /** 7 | * Validates against a list of validators. 8 | * 9 | * @param {...function} validators - Validators to validate against. 10 | * @return {function} - A function that takes a value and that returns true or false if valid or not. 11 | */ 12 | export default function oneOf(...validators) { 13 | if (!validators.length) { 14 | throw new Error('You need to use at least one validator.'); 15 | } 16 | 17 | return (input, info) => { 18 | if (info) { 19 | const types = []; 20 | const converters = []; 21 | for (const validator of validators) { 22 | const result = createInfoObject({ validator }); 23 | types.push(result.type); 24 | if (result.converter) { 25 | converters.push(result.converter); 26 | } 27 | } 28 | return createInfoObject({ 29 | validator: types.join(' / '), 30 | converter: converters.length > 0 ? () => convert(...converters) : undefined, 31 | }); 32 | } 33 | 34 | const invalid = []; 35 | for (const validator of validators) { 36 | const result = isValid(input, validator); 37 | if (result === true) { 38 | return true; 39 | } 40 | invalid.push(getInfoObject(validator).type || 'Unknown type'); 41 | } 42 | 43 | /* eslint-disable prefer-template */ 44 | return 'Was not any of the possible types:\n' + 45 | invalid.reduce((prev, next) => prev + '\n' + next, ''); 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/require/patchResolveFilename.js: -------------------------------------------------------------------------------- 1 | const Module = require('module'); 2 | const path = require('path'); 3 | 4 | const log = require('debug')('roc:core:require'); 5 | 6 | const originalResolveFilename = Module._resolveFilename; 7 | 8 | export default function patchResolveFilename(resolveRequest) { 9 | log('Initializing'); 10 | 11 | Module._resolveFilename = function rocResolveFilename(request, parent) { 12 | // Get the context for the request, the directory that the request is coming from. 13 | // We are using "dirname" here to remove "node_modules" from the path that will be 14 | // present given how Nodes algorithm works, with the first path being the directory 15 | // from which the request was performed. 16 | // Example: [/dir/that/did/the/request/node_modules, /dir/that/did/the/node_modules, ...] 17 | const context = parent && parent.paths 18 | ? path.dirname(parent.paths[0]) 19 | : undefined; 20 | 21 | try { 22 | return originalResolveFilename.apply(this, [ 23 | resolveRequest(request, context), 24 | parent, 25 | ]); 26 | } catch (_error) { 27 | /* We try again with fallback enabled. 28 | * This emulates kinda how NODE_PATH works in that we try again with another scope. 29 | * What this does is that it uses the context of dependencies for the extension 30 | * that a dependency is installed in to manage possible failures. This is needed 31 | * if a dependency of an extension requires some peerDependency that some other 32 | * extension is providing. 33 | */ 34 | return originalResolveFilename.apply(this, [ 35 | resolveRequest(request, context, { fallback: true }), 36 | parent, 37 | ]); 38 | } 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/validation/validateSettings.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject, isFunction } from 'lodash'; 2 | 3 | import isValid from './helpers/isValid'; 4 | import throwValidationError from './helpers/throwValidationError'; 5 | import { REQUIRED_ERROR } from './validators/required'; 6 | 7 | /** 8 | * Validates the provided settings objects and might throw. 9 | */ 10 | export default function validateSettings(settings = {}, meta = {}, allowRequiredFailure = false, path = 'settings') { 11 | const validateKeys = Object.keys(meta); 12 | 13 | for (const validateKey of validateKeys) { 14 | const configValue = settings[validateKey]; 15 | const validator = meta[validateKey].validator && isFunction(meta[validateKey].validator) ? 16 | meta[validateKey].validator : 17 | undefined; 18 | const newPath = path ? `${path}.${validateKey}` : validateKey; 19 | 20 | // process validation nodes recursively 21 | if (isPlainObject(configValue) && !validator) { 22 | validateSettings(configValue, meta[validateKey], allowRequiredFailure, newPath); 23 | } else { 24 | assertValid(configValue, newPath, validator, allowRequiredFailure); 25 | } 26 | } 27 | } 28 | 29 | function assertValid(value, key, validator, allowRequiredFailure = false) { 30 | const result = isValid(value, validator); 31 | if ( 32 | !(allowRequiredFailure && result === REQUIRED_ERROR) && 33 | result !== true 34 | ) { 35 | throwValidationError(...processResult(key, result, value), 'property'); 36 | } 37 | } 38 | 39 | function processResult(key, result, value) { 40 | if (isPlainObject(result)) { 41 | return [ 42 | `${key}${result.key}`, 43 | result.message, 44 | result.value, 45 | ]; 46 | } 47 | return [ 48 | key, 49 | result, 50 | value, 51 | ]; 52 | } 53 | -------------------------------------------------------------------------------- /src/require/manageDependencies.js: -------------------------------------------------------------------------------- 1 | export function initGetDependencies({ extensionsDependencies }) { 2 | return (name, selector) => { 3 | if (name) { 4 | return selector ? 5 | (extensionsDependencies[name] || {})[selector] : 6 | extensionsDependencies[name]; 7 | } 8 | 9 | return undefined; 10 | }; 11 | } 12 | 13 | export function initGetDependenciesFromPath({ extensionsDependencies, pathsToExtensions }) { 14 | return (path, selector) => 15 | initGetDependencies({ extensionsDependencies })( 16 | pathsToExtensions[Object.keys(pathsToExtensions).find((extensionPath) => path.startsWith(extensionPath))], 17 | selector 18 | ); 19 | } 20 | 21 | export function initSetDependencies({ extensionsDependencies, pathsToExtensions }) { 22 | return (name, extensionDependencies, { dependencies } = {}, path) => { 23 | const newExtensionsDependencies = { 24 | ...extensionsDependencies, 25 | [name]: { 26 | ...extensionDependencies, 27 | exports: removeFromExports(extensionDependencies.exports, dependencies), 28 | }, 29 | }; 30 | 31 | const newPathsToExtensions = path ? 32 | { ...pathsToExtensions, [path]: name } : 33 | pathsToExtensions; 34 | 35 | return { 36 | extensionsDependencies: newExtensionsDependencies, 37 | pathsToExtensions: newPathsToExtensions, 38 | }; 39 | }; 40 | } 41 | 42 | // Removes dependencies that exists in the package.json for the extension in question 43 | function removeFromExports(exports, dependencies = {}) { 44 | const localExports = { ...exports }; 45 | Object.keys(localExports).forEach((exported) => { 46 | if (dependencies[exported]) { 47 | delete localExports[exported]; 48 | } 49 | }); 50 | return localExports; 51 | } 52 | -------------------------------------------------------------------------------- /test/validation/validators/isObject.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isArray from '../../../src/validation/validators/isArray'; 4 | import isObject from '../../../src/validation/validators/isObject'; 5 | import isPath from '../../../src/validation/validators/isPath'; 6 | import toObject from '../../../src/converters/toObject'; 7 | 8 | describe('validators', () => { 9 | describe('isObject', () => { 10 | it('should return a wrapped validator', () => { 11 | expect(isObject()) 12 | .toBeA('function'); 13 | }); 14 | 15 | it('should return info object if requested', () => { 16 | const validator = () => ({ 17 | type: 'Type', 18 | required: false, 19 | canBeEmpty: true, 20 | converter: toObject, 21 | }); 22 | 23 | expect(isObject(validator)(null, true)) 24 | .toEqual({ 25 | type: 'Object(Type)', 26 | required: false, 27 | canBeEmpty: true, 28 | converter: toObject, 29 | unmanagedObject: false, 30 | }); 31 | }); 32 | 33 | it('should return error if value is not a plain object', () => { 34 | expect(isObject()([])) 35 | .toInclude('not an object'); 36 | }); 37 | 38 | it('should validate a object correctly', () => { 39 | expect(isObject(() => true)({})) 40 | .toBe(true); 41 | }); 42 | 43 | it('should validate if no validator is given and it is a plain object', () => { 44 | expect(isObject()({})) 45 | .toBe(true); 46 | }); 47 | 48 | it('should validate complex object as valid', () => { 49 | expect(isObject(isArray(isPath))({ a: ['/some/path'] })) 50 | .toBe(true); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/validation/validators/oneOf.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isBoolean from '../../../src/validation/validators/isBoolean'; 4 | import isInteger from '../../../src/validation/validators/isInteger'; 5 | import isString from '../../../src/validation/validators/isString'; 6 | import oneOf from '../../../src/validation/validators/oneOf'; 7 | 8 | describe('validators', () => { 9 | describe('oneOf', () => { 10 | it('should return a wrapped validator', () => { 11 | expect(oneOf(isString)) 12 | .toBeA('function'); 13 | }); 14 | 15 | it('should return info object if requested', () => { 16 | const validator = () => ({ 17 | type: 'Type', 18 | required: false, 19 | }); 20 | 21 | expect(oneOf(validator, validator, validator)(null, true)) 22 | .toEqual({ 23 | type: 'Type / Type / Type', 24 | required: false, 25 | canBeEmpty: null, 26 | converter: undefined, 27 | unmanagedObject: false, 28 | }); 29 | }); 30 | 31 | it('should return error if value is invalid', () => { 32 | expect(oneOf(/a/, isInteger, isBoolean)('true')) 33 | .toEqual('Was not any of the possible types:\n\n/a/\nInteger\nBoolean'); 34 | }); 35 | 36 | it('should validate correctly', () => { 37 | expect(oneOf(isInteger, isBoolean)(1)) 38 | .toBe(true); 39 | }); 40 | 41 | it('should succeed fast', () => { 42 | const spy = expect.createSpy().andReturn(true); 43 | oneOf(spy, spy, spy)(1); 44 | 45 | expect(spy.calls.length).toBe(1); 46 | }); 47 | 48 | it('should throw if no validator is given', () => { 49 | expect(() => oneOf()) 50 | .toThrow(); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/hooks/runHook.js: -------------------------------------------------------------------------------- 1 | import log from '../log/default/large'; 2 | import getSuggestions from '../helpers/getSuggestions'; 3 | 4 | import { getHooks } from './manageHooks'; 5 | import runHookDirectly from './runHookDirectly'; 6 | 7 | /** 8 | * Used to invoke a hook that have been initialized using registerHooks. 9 | * 10 | * @example 11 | * // First function takes the extension name, the second takes the hook name and possible 12 | * // arguments and the third will take a callback that will receive a single value. 13 | * // The callback is expected if "hasCallback" was set to true. 14 | * (extensionName) => (hookName, ...arguments) => (potentialCallback) 15 | * 16 | * @param {string} extensionName - The extension that the hook belongs to. 17 | * 18 | * @returns {function} - Will return a function that takes in the name of the hook and potential arguments. 19 | */ 20 | export default function runHook(extensionName) { 21 | const allHooks = getHooks(); 22 | const extensionHooks = allHooks[extensionName]; 23 | 24 | if (!extensionHooks) { 25 | log.error( 26 | `The given extension is not registered.\n\n${getSuggestions([extensionName], Object.keys(allHooks))}`, 27 | 'Hook problem' 28 | ); 29 | } 30 | 31 | return (name, ...args) => { 32 | if (!extensionHooks[name]) { 33 | log.error( 34 | `The given hook is not registered.\n\n${getSuggestions([name], Object.keys(extensionHooks))}`, 35 | 'Hook problem' 36 | ); 37 | } 38 | 39 | if (extensionHooks[name].hasCallback) { 40 | return (callback) => 41 | runHookDirectly({ 42 | ...extensionHooks[name], 43 | extension: extensionName, 44 | name, 45 | }, args, callback); 46 | } 47 | 48 | return runHookDirectly({ 49 | ...extensionHooks[name], 50 | extension: extensionName, 51 | name, 52 | }, args); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /test/context/extensions/helpers/updateExtensions.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import updateExtensions from '../../../../src/context/extensions/helpers/updateExtensions'; 4 | 5 | describe('context', () => { 6 | describe('extensions', () => { 7 | describe('helpers', () => { 8 | describe('updateExtensions', () => { 9 | it('should update an object correctly', () => { 10 | const current = { 11 | a: { 12 | value: true, 13 | __extensions: [1, 2], 14 | }, 15 | b: { 16 | d: { 17 | value: true, 18 | __extensions: [2], 19 | }, 20 | value: true, 21 | __extensions: [1], 22 | }, 23 | }; 24 | const old = { 25 | a: { 26 | __extensions: [1, 3], 27 | }, 28 | b: { 29 | c: { 30 | __extensions: [4], 31 | }, 32 | __extensions: [5], 33 | }, 34 | }; 35 | expect(updateExtensions(current, old)).toEqual({ 36 | a: { 37 | value: true, 38 | __extensions: [1, 2, 3], 39 | }, 40 | b: { 41 | d: { 42 | value: true, 43 | __extensions: [2], 44 | }, 45 | value: true, 46 | __extensions: [1, 5], 47 | }, 48 | }); 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/context/initContext.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import expect from 'expect'; 4 | 5 | import initContext from '../../src/context/initContext'; 6 | 7 | describe('initContext', () => { 8 | it('should register init and run correctly in project', () => { 9 | const projectPath = path.join(__dirname, 'fixtures', 'projects', 'init'); 10 | const initSpy = expect.spyOn(require(path.join(projectPath, 'roc.config.js')).project, 'init') 11 | .andReturn({ 12 | roc: { 13 | meta: { a: 1 }, 14 | }, 15 | }); 16 | const context = initContext({ 17 | directory: projectPath, 18 | }); 19 | 20 | expect(initSpy).toHaveBeenCalled(); 21 | expect(context.meta).toEqual({ a: 1 }); 22 | }); 23 | 24 | it('should register actions in project, if array', () => { 25 | const multipleActions = [ 26 | () => {}, 27 | { 28 | hook: 'a', 29 | action: () => {}, 30 | }, 31 | ]; 32 | 33 | const projectPath = path.join(__dirname, 'fixtures', 'projects', 'actions'); 34 | require(path.join(projectPath, 'roc.config.js')).project.actions = multipleActions; 35 | 36 | const context = initContext({ 37 | directory: projectPath, 38 | }); 39 | 40 | expect(context.actions[0].actions[0].action).toBe(multipleActions[0]); 41 | expect(context.actions[0].actions[1].action).toBe(multipleActions[1].action); 42 | expect(context.actions[0].actions[1].hook).toBe(multipleActions[1].hook); 43 | }); 44 | 45 | it('should register actions in project, if function', () => { 46 | const singleAction = () => {}; 47 | 48 | const projectPath = path.join(__dirname, 'fixtures', 'projects', 'actions'); 49 | require(path.join(projectPath, 'roc.config.js')).project.actions = singleAction; 50 | 51 | const context = initContext({ 52 | directory: projectPath, 53 | }); 54 | 55 | expect(context.actions[0].actions[0].action).toBe(singleAction); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/converters/convert.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import { toArray, toRegExp, toBoolean, toInteger, toObject, convert } from '../../src/converters'; 4 | 5 | describe('converters', () => { 6 | describe('convert', () => { 7 | it('should convert the value toBoolean', () => { 8 | expect(() => convert()).toThrow('You need to use at least one converter.'); 9 | }); 10 | 11 | it('should convert the value toBoolean', () => { 12 | expect(convert(toBoolean, toInteger)('true')).toEqual(true); 13 | }); 14 | 15 | it('should convert the value toInteger', () => { 16 | expect(convert(toBoolean, toInteger)('100')).toEqual(100); 17 | 18 | expect(convert(toBoolean, toInteger)(100)).toEqual(100); 19 | }); 20 | 21 | it('should convert the value toArray', () => { 22 | expect(convert(toBoolean, toArray())('100,100,100')).toEqual(['100', '100', '100']); 23 | 24 | expect(convert(toBoolean, toArray())('[100, 100, 100]')).toEqual([100, 100, 100]); 25 | }); 26 | 27 | it('should convert the value toBoolean', () => { 28 | expect(convert(toBoolean, toArray, toRegExp, toInteger, toObject)('true')).toEqual(true); 29 | 30 | expect(convert(toBoolean, toArray, toRegExp, toInteger, toObject)('false')).toEqual(false); 31 | }); 32 | 33 | it('should convert the value toBoolean or toInteger', () => { 34 | expect(convert(toBoolean, toInteger)('true')).toEqual(true); 35 | 36 | expect(convert(toBoolean, toInteger)('100')).toEqual(100); 37 | }); 38 | 39 | it('should return undefined if it could not be converted', () => { 40 | expect(convert(toBoolean, toInteger)('{}')).toEqual(undefined); 41 | }); 42 | 43 | it('should be possible to define a custom converter', () => { 44 | expect(convert((input) => { 45 | if (input === 'custom') { 46 | return true; 47 | } 48 | 49 | return false; 50 | })('custom')).toEqual(true); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/commands/init/generateTemplate/options.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | import validateName from 'validate-npm-package-name'; 4 | 5 | import fileExists from '../../../helpers/fileExists'; 6 | import log from '../../../log/default/small'; 7 | 8 | import getGitUser from './getGitUser'; 9 | 10 | export default function options(name, dir) { 11 | const opts = getSetupData(dir); 12 | 13 | setDefault(opts, 'name', name); 14 | setValidateName(opts); 15 | 16 | const { name: author } = getGitUser(); 17 | if (author) { 18 | setDefault(opts, 'author', author); 19 | } 20 | 21 | return opts; 22 | } 23 | 24 | function getSetupData(dir) { 25 | const json = path.join(dir, 'roc.setup.json'); 26 | const js = path.join(dir, 'roc.setup.js'); 27 | 28 | let opts = {}; 29 | 30 | if (fileExists(json)) { 31 | opts = require(json); // eslint-disable-line 32 | } else if (fileExists(js)) { 33 | const req = require(path.resolve(js)); // eslint-disable-line 34 | if (req !== Object(req)) { 35 | log.error('roc.setup.js needs to export an object'); 36 | } 37 | 38 | opts = req; 39 | } 40 | 41 | return opts; 42 | } 43 | 44 | /** 45 | * Set the default value for a prompt question 46 | */ 47 | function setDefault(opts, key, val) { 48 | const prompts = opts.prompts || (opts.prompts = {}); // eslint-disable-line 49 | if (prompts[key]) { 50 | prompts[key].default = val; 51 | } else if (prompts[key] && typeof prompts[key] !== 'object') { 52 | prompts[key] = { 53 | type: 'string', 54 | default: val, 55 | }; 56 | } 57 | } 58 | 59 | function setValidateName(opts) { 60 | if (opts.prompts.name) { 61 | // eslint-disable-next-line 62 | opts.prompts.name.validate = (name) => { 63 | const its = validateName(name); 64 | 65 | if (!its.validForNewPackages) { 66 | const errors = (its.errors || []).concat(its.warnings || []); 67 | return `Sorry, ${errors.join(' and ')}.`; 68 | } 69 | 70 | return true; 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/validation/validators/isObject.js: -------------------------------------------------------------------------------- 1 | import { isPlainObject } from 'lodash'; 2 | 3 | import createInfoObject from '../helpers/createInfoObject'; 4 | import isValid from '../helpers/isValid'; 5 | import toObject from '../../converters/toObject'; 6 | 7 | /** 8 | * Validates an object using a validator. 9 | * 10 | * @param {function|RegExp} validator - The validator to use on the elements in the object 11 | * @param {Object} options - Uptions that should be used with the validator 12 | * @return {function} - A function that takes a value and that returns true or false if valid or not. 13 | */ 14 | export default function isObject(...args) { 15 | const validator = isPlainObject(args[0]) ? undefined : args[0]; 16 | const { unmanaged = false } = (isPlainObject(args[0]) ? args[0] : args[1]) || {}; 17 | 18 | return (input, info) => { 19 | if (info) { 20 | return createInfoObject({ 21 | validator, 22 | converter: () => toObject, 23 | wrapper: (wrap) => `Object(${wrap})`, 24 | canBeEmpty: true, 25 | unmanagedObject: unmanaged, 26 | }); 27 | } 28 | 29 | if (input === undefined || input === null) { 30 | return true; 31 | } 32 | 33 | if (!isPlainObject(input)) { 34 | return 'Was not an object!'; 35 | } 36 | 37 | if (!validator) { 38 | return true; 39 | } 40 | 41 | for (const key of Object.keys(input)) { 42 | const result = isValid(input[key], validator); 43 | if (result !== true) { 44 | if (isPlainObject(result)) { 45 | return { 46 | key: `.${key}${result.key}`, 47 | value: result.value, 48 | message: result.message, 49 | }; 50 | } 51 | return { 52 | key: `.${key}`, 53 | value: input[key], 54 | message: result, 55 | }; 56 | } 57 | } 58 | 59 | return true; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /test/validation/validators/required.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isArray from '../../../src/validation/validators/isArray'; 4 | import isObject from '../../../src/validation/validators/isObject'; 5 | import isString from '../../../src/validation/validators/isString'; 6 | import required from '../../../src/validation/validators/required'; 7 | 8 | describe('validators', () => { 9 | describe('require', () => { 10 | it('should return a wrapped validator', () => { 11 | expect(required()) 12 | .toBeA('function'); 13 | }); 14 | 15 | it('should return info object if requested', () => { 16 | const validator = () => ({ 17 | type: 'Type', 18 | required: false, 19 | canBeEmpty: null, 20 | }); 21 | 22 | expect(required(validator)(null, true)) 23 | .toEqual({ 24 | type: 'Type', 25 | required: true, 26 | canBeEmpty: null, 27 | converter: undefined, 28 | unmanagedObject: false, 29 | }); 30 | }); 31 | 32 | it('should validate correctly when value is provided', () => { 33 | expect(required()(true)) 34 | .toBe(true); 35 | 36 | expect(required(isString)('')) 37 | .toBe(true); 38 | 39 | expect(required(isObject())({})) 40 | .toBe(true); 41 | 42 | expect(required(isArray())([])) 43 | .toBe(true); 44 | }); 45 | 46 | it('should return error if no value is provided', () => { 47 | expect(required()()) 48 | .toInclude('A value was required but none was given!'); 49 | 50 | expect(required(isString)()) 51 | .toInclude('A value was required but none was given!'); 52 | 53 | expect(required(isObject())()) 54 | .toInclude('A value was required but none was given!'); 55 | 56 | expect(required(isArray())()) 57 | .toInclude('A value was required but none was given!'); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /src/context/extensions/steps/processNormalExports.js: -------------------------------------------------------------------------------- 1 | import { initSetDependencies } from '../../../require/manageDependencies'; 2 | 3 | /* 4 | We want to make all of the dependencies that the "normal" one has available to the development one 5 | since the development one might use some of the "normal" dependencies when running. 6 | An example of this is where a development extension includes some code that has a dependency on something that is 7 | provided from the "normal" extension. This could be code that wraps a dependency as an example. 8 | */ 9 | export default function processNormalExports(initialState) { 10 | let dependencyContext = { ...initialState.dependencyContext }; 11 | initialState.context.usedExtensions.forEach(({ name, packageJSON }) => { 12 | // If the name ends in -dev we will remove the -dev part and use that to find dependencies 13 | const normalName = /-dev$/.test(name) && name.slice(0, -4); 14 | if (normalName && initialState.temp.extensionsNormalExports[normalName]) { 15 | dependencyContext = 16 | initSetDependencies(dependencyContext)( 17 | name, 18 | { 19 | ...dependencyContext.extensionsDependencies[name], 20 | exports: { 21 | // We add the "normal" exports first since we do not want to overwrite something 22 | // that comes from the development ones because that could result in different 23 | // dependencies in development and production 24 | ...initialState.temp.extensionsNormalExports[normalName], 25 | ...dependencyContext.extensionsDependencies[name].exports, 26 | }, 27 | }, 28 | // Remove things that are defined in the package.json directly 29 | // This will avoid that different dependencies are used in development and production 30 | packageJSON 31 | ); 32 | } 33 | }); 34 | 35 | return { 36 | ...initialState, 37 | dependencyContext, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /test/validation/validators/notEmpty.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | 3 | import isArray from '../../../src/validation/validators/isArray'; 4 | import isObject from '../../../src/validation/validators/isObject'; 5 | import isString from '../../../src/validation/validators/isString'; 6 | import notEmpty from '../../../src/validation/validators/notEmpty'; 7 | 8 | describe('validators', () => { 9 | describe('notEmpty', () => { 10 | it('should return a wrapped validator', () => { 11 | expect(notEmpty()) 12 | .toBeA('function'); 13 | }); 14 | 15 | it('should return infoObject if requested', () => { 16 | const validator = () => ({ 17 | type: 'Type', 18 | canBeEmpty: false, 19 | }); 20 | 21 | expect(notEmpty(validator)(null, true)) 22 | .toEqual({ 23 | type: 'Type', 24 | required: false, 25 | canBeEmpty: false, 26 | converter: undefined, 27 | unmanagedObject: false, 28 | }); 29 | }); 30 | 31 | it('should validate correctly when value is provided', () => { 32 | expect(notEmpty()(true)) 33 | .toBe(true); 34 | 35 | expect(notEmpty(isString)('a')) 36 | .toBe(true); 37 | 38 | expect(notEmpty(isObject())({ a: 1 })) 39 | .toBe(true); 40 | 41 | expect(notEmpty(isArray())([1])) 42 | .toBe(true); 43 | }); 44 | 45 | it('should validate if no validator is given and it is an array', () => { 46 | expect(notEmpty()(1)) 47 | .toBe(true); 48 | }); 49 | 50 | it('should return error if no value is provided', () => { 51 | expect(notEmpty(isString)('')) 52 | .toInclude('The value is required to not be empty!'); 53 | 54 | expect(notEmpty(isObject())({})) 55 | .toInclude('The value is required to not be empty!'); 56 | 57 | expect(notEmpty(isArray())([])) 58 | .toInclude('The value is required to not be empty!'); 59 | }); 60 | }); 61 | }); 62 | --------------------------------------------------------------------------------