diff --git a/.all-contributorsrc b/.all-contributorsrc
index 348f491..65d0870 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -219,6 +219,15 @@
"code",
"test"
]
+ },
+ {
+ "login": "erikfox",
+ "name": "Erik Fox",
+ "avatar_url": "https://avatars.githubusercontent.com/u/2915616?v=3",
+ "profile": "http://www.erikfox.co/",
+ "contributions": [
+ "bug"
+ ]
}
]
}
diff --git a/README.md b/README.md
index f2eccde..7306ad8 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ All the benefits of npm scripts without the cost of a bloated package.json and l
[![downloads][downloads-badge]][npm-stat]
[![MIT License][license-badge]][LICENSE]
-[![All Contributors](https://img.shields.io/badge/all_contributors-21-orange.svg?style=flat-square)](#contributors)
+[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors)
[![PRs Welcome][prs-badge]][prs]
[![Donate][donate-badge]][donate]
[![Code of Conduct][coc-badge]][coc]
@@ -431,6 +431,7 @@ Thanks goes to these people ([emoji key][emojis]):
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [
Tommy](http://www.tommyleunen.com)
[🐛](https://github.com/kentcdodds/p-s/issues?q=author%3Atleunen) [💻](https://github.com/kentcdodds/p-s/commits?author=tleunen) [⚠️](https://github.com/kentcdodds/p-s/commits?author=tleunen) 👀 | [
Jayson Harshbarger](http://www.hypercubed.com)
💡 👀 | [
JD Isaacks](http://www.jisaacks.com)
[💻](https://github.com/kentcdodds/p-s/commits?author=jisaacks) [⚠️](https://github.com/kentcdodds/p-s/commits?author=jisaacks) | [
Christopher Hiller](https://boneskull.com)
👀 | [
Robin Malfait](https://robinmalfait.com)
💡 | [
Eric McCormick](https://ericmccormick.io)
👀 [📖](https://github.com/kentcdodds/p-s/commits?author=edm00se) | [
Sam Verschueren](https://twitter.com/SamVerschueren)
👀 |
| [
Sorin Muntean](https://github.com/sxn)
[💻](https://github.com/kentcdodds/p-s/commits?author=sxn) [⚠️](https://github.com/kentcdodds/p-s/commits?author=sxn) [📖](https://github.com/kentcdodds/p-s/commits?author=sxn) | [
Keith Gunn](https://github.com/gunnx)
[🐛](https://github.com/kentcdodds/p-s/issues?q=author%3Agunnx) [💻](https://github.com/kentcdodds/p-s/commits?author=gunnx) [⚠️](https://github.com/kentcdodds/p-s/commits?author=gunnx) | [
Joe Martella](http://martellaj.github.io)
[🐛](https://github.com/kentcdodds/p-s/issues?q=author%3Amartellaj) [💻](https://github.com/kentcdodds/p-s/commits?author=martellaj) [⚠️](https://github.com/kentcdodds/p-s/commits?author=martellaj) | [
Martin Segado](https://github.com/msegado)
[📖](https://github.com/kentcdodds/p-s/commits?author=msegado) | [
Bram Borggreve](http://colmena.io/)
[🐛](https://github.com/kentcdodds/p-s/issues?q=author%3Abeeman) [💻](https://github.com/kentcdodds/p-s/commits?author=beeman) | [
Elijah Manor](http://elijahmanor.com)
📹 | [
Ragu Ramaswamy](https://github.com/rrag)
[💻](https://github.com/kentcdodds/p-s/commits?author=rrag) [⚠️](https://github.com/kentcdodds/p-s/commits?author=rrag) |
+| [
Erik Fox](http://www.erikfox.co/)
[🐛](https://github.com/kentcdodds/p-s/issues?q=author%3Aerikfox) |
This project follows the [all-contributors][all-contributors] specification.
diff --git a/other/ERRORS_AND_WARNINGS.md b/other/ERRORS_AND_WARNINGS.md
index e719f8e..f39404b 100644
--- a/other/ERRORS_AND_WARNINGS.md
+++ b/other/ERRORS_AND_WARNINGS.md
@@ -45,3 +45,11 @@ This means that the child process for the specified script emitted an error.
Look at the error and try to figure out why the script would be failing.
Try to run the script without `p-s` and verify that the script is working. If not, fix that. If it's working without `p-s` it could be a problem with `p-s`. Please file an issue.
+
+## Config Must be an Object
+
+Your `package-scripts.js`, `package-scripts.yml`, or whatever you specified as the `--config` must be an object or a function that returns an object.
+
+### To Fix:
+
+Make sure that your config is an object or a function that returns an object.
diff --git a/package.json b/package.json
index b308323..6613722 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,8 @@
"prefix-matches": "^0.0.9",
"readline-sync": "^1.4.6",
"shell-escape": "^0.2.0",
- "spawn-command-with-kill": "^1.0.0"
+ "spawn-command-with-kill": "^1.0.0",
+ "type-detect": "^4.0.0"
},
"devDependencies": {
"all-contributors-cli": "^3.1.0",
diff --git a/src/bin-utils/fixtures/bad-data-type-config.js b/src/bin-utils/fixtures/bad-data-type-config.js
new file mode 100644
index 0000000..a08019b
--- /dev/null
+++ b/src/bin-utils/fixtures/bad-data-type-config.js
@@ -0,0 +1 @@
+module.exports = 'cannot be a string'
\ No newline at end of file
diff --git a/src/bin-utils/fixtures/bad-function-data-type-config.js b/src/bin-utils/fixtures/bad-function-data-type-config.js
new file mode 100644
index 0000000..e316e1b
--- /dev/null
+++ b/src/bin-utils/fixtures/bad-function-data-type-config.js
@@ -0,0 +1 @@
+module.exports = () => ['cannot be a function that returns an array']
\ No newline at end of file
diff --git a/src/bin-utils/fixtures/function-config.js b/src/bin-utils/fixtures/function-config.js
new file mode 100644
index 0000000..b022e5b
--- /dev/null
+++ b/src/bin-utils/fixtures/function-config.js
@@ -0,0 +1,5 @@
+module.exports = () => ({
+ scripts: {
+ functionConfig: 'echo worked!',
+ }
+})
\ No newline at end of file
diff --git a/src/bin-utils/index.js b/src/bin-utils/index.js
index 987f0f1..72a1301 100644
--- a/src/bin-utils/index.js
+++ b/src/bin-utils/index.js
@@ -1,6 +1,7 @@
import {resolve} from 'path'
import {readFileSync} from 'fs'
-import {remove, includes, isPlainObject, isEmpty} from 'lodash'
+import {remove, includes, isPlainObject, isUndefined, isEmpty, isFunction} from 'lodash'
+import typeOf from 'type-detect'
import shellEscape from 'shell-escape'
import colors from 'colors/safe'
import {safeLoad} from 'js-yaml'
@@ -35,15 +36,38 @@ const loadJSConfig = getAttemptModuleRequireFn(function onFail(configPath, requi
/**
* Attempts to load the config and logs an error if there's a problem
- * @param {String} configPath The path to attempt to require the config from
- * @return {*} The required module
+ * @param {String} configPath The path to attempt to require the config from
+ * @param {*} input the input to pass to the config if it's a function
+ * @return {Object} The config
*/
-function loadConfig(configPath) {
+function loadConfig(configPath, input) {
if (configPath.endsWith('.yml')) {
return loadYAMLConfig(configPath)
}
- return loadJSConfig(configPath)
+ let config = loadJSConfig(configPath)
+ if (isUndefined(config)) {
+ // let the caller deal with this
+ return config
+ }
+ let typeMessage
+ if (isFunction(config)) {
+ config = config(input)
+ typeMessage = `Your config was a function that returned a data type of "${typeOf(config)}"`
+ } else {
+ typeMessage = `Your config data type was "${typeOf(config)}"`
+ }
+ if (!isPlainObject(config)) {
+ log.error({
+ message: colors.red(
+ `The package-scripts configuration ("${configPath}") ` +
+ 'must be an object or a function that returns an object.',
+ ),
+ ref: 'config-must-be-an-object',
+ })
+ throw new Error(typeMessage)
+ }
+ return config
}
export {
diff --git a/src/bin-utils/index.test.js b/src/bin-utils/index.test.js
index 9449efc..eef8af4 100644
--- a/src/bin-utils/index.test.js
+++ b/src/bin-utils/index.test.js
@@ -1,5 +1,5 @@
/* eslint import/newline-after-import:0, global-require:0 */
-import {resolve} from 'path'
+import path from 'path'
import colors from 'colors/safe'
import {spy} from 'sinon'
import {getScriptsAndArgs, help, preloadModule, loadConfig} from './index'
@@ -56,8 +56,7 @@ test('preloadModule: resolves a relative path', () => {
})
test('preloadModule: resolves an absolute path', () => {
- const relativePath = './fixtures/my-module'
- const absolutePath = resolve(__dirname, relativePath)
+ const absolutePath = getAbsoluteFixturePath('my-module')
const val = preloadModule(absolutePath)
expect(val).toBe('hello')
})
@@ -74,11 +73,50 @@ test('preloadModule: logs a warning when the module cannot be required', () => {
const {preloadModule: proxiedPreloadModule} = require('./index')
const val = proxiedPreloadModule('./module-that-does-exist')
expect(val).toBeUndefined()
- expect(mockWarn.calledOnce)
+ expect(mockWarn.calledOnce).toBe(true)
const [{message}] = mockWarn.firstCall.args
expect(message).toMatch(/Unable to preload "\.\/module-that-does-exist"/)
})
+test('loadConfig: calls the config function if it is a function', () => {
+ jest.resetModules()
+ const {loadConfig: proxiedLoadConfig} = require('./index')
+ const val = proxiedLoadConfig(getAbsoluteFixturePath('function-config.js'))
+ expect(val).toEqual({
+ scripts: {
+ functionConfig: 'echo worked!',
+ },
+ })
+})
+
+test('loadConfig: logs and throws an error for a config that exports the wrong data type', () => {
+ const mockError = jest.fn()
+ jest.resetModules()
+ jest.mock('../get-logger', () => () => ({error: mockError}))
+ const {loadConfig: proxiedLoadConfig} = require('./index')
+ const fixturePath = getAbsoluteFixturePath('bad-data-type-config.js')
+ expect(() => proxiedLoadConfig(fixturePath)).toThrowError(/Your config.*string/)
+ expect(mockError).toHaveBeenCalledTimes(1)
+ expect(mockError).toHaveBeenCalledWith({
+ message: expect.stringMatching(fixturePath),
+ ref: 'config-must-be-an-object',
+ })
+})
+
+test('loadConfig: logs and throws an error for a config that exports a function that returns the wrong data type', () => {
+ const mockError = jest.fn()
+ jest.resetModules()
+ jest.mock('../get-logger', () => () => ({error: mockError}))
+ const {loadConfig: proxiedLoadConfig} = require('./index')
+ const fixturePath = getAbsoluteFixturePath('bad-function-data-type-config.js')
+ expect(() => proxiedLoadConfig(fixturePath)).toThrowError(/Your config.*function.*Array/)
+ expect(mockError).toHaveBeenCalledTimes(1)
+ expect(mockError).toHaveBeenCalledWith({
+ message: expect.stringMatching(fixturePath),
+ ref: 'config-must-be-an-object',
+ })
+})
+
test('loadConfig: logs a warning when the JS module cannot be required', () => {
const mockError = spy()
jest.resetModules()
@@ -93,7 +131,7 @@ test('loadConfig: logs a warning when the JS module cannot be required', () => {
test('loadConfig: does not swallow JS syntax errors', () => {
const originalCwd = process.cwd
- process.cwd = jest.fn(() => resolve(__dirname, '../..'))
+ process.cwd = jest.fn(() => path.resolve(__dirname, '../..'))
const relativePath = './src/bin-utils/fixtures/syntax-error-module'
expect(() => loadConfig(relativePath)).toThrowError()
process.cwd = originalCwd
@@ -112,7 +150,7 @@ test('loadConfig: can load ES6 module', () => {
test('loadConfig: does not swallow YAML syntax errors', () => {
const originalCwd = process.cwd
- process.cwd = jest.fn(() => resolve(__dirname, '../..'))
+ process.cwd = jest.fn(() => path.resolve(__dirname, '../..'))
const relativePath = './src/bin-utils/fixtures/syntax-error-config.yml'
expect(() => loadConfig(relativePath)).toThrowError()
process.cwd = originalCwd
@@ -217,3 +255,7 @@ test('help: do not display scripts with flag hiddenFromHelp set to true', () =>
const expected = colors.yellow('There are no scripts available')
expect(message).toBe(expected)
})
+
+function getAbsoluteFixturePath(fixture) {
+ return path.join(__dirname, 'fixtures', fixture)
+}
diff --git a/src/bin/nps.js b/src/bin/nps.js
index a199f21..4cd1680 100644
--- a/src/bin/nps.js
+++ b/src/bin/nps.js
@@ -32,13 +32,13 @@ program
.on('--help', onHelp)
.parse(process.argv)
+const scriptsAndArgs = getScriptsAndArgs(program)
if (shouldAutocomplete) {
autocomplete(getPSConfig())
} else if (shouldRun) {
const psConfig = getPSConfig()
const hasDefaultScript = !!psConfig.scripts.default
- const scriptsAndArgs = getScriptsAndArgs(program)
const hasHelpScript = !!psConfig.scripts.help
const scriptIsHelp = scriptsAndArgs.scripts[0] === 'help'
const scriptSpecified = scriptsAndArgs.scripts.length >= 1
@@ -47,11 +47,11 @@ if (shouldAutocomplete) {
} else if (!hasHelpScript && scriptIsHelp) { // eslint-disable-line no-negated-condition
program.outputHelp()
} else {
- loadAndRun(scriptsAndArgs, psConfig)
+ loadAndRun(psConfig)
}
}
-function loadAndRun(scriptsAndArgs, psConfig) {
+function loadAndRun(psConfig) {
runPackageScript({
scriptConfig: psConfig.scripts,
scripts: scriptsAndArgs.scripts,
@@ -76,7 +76,7 @@ function getPSConfig() {
log.warn(colors.yellow('Unable to find a config file and none was specified.'))
return {scripts: {}} // empty config
}
- const config = loadConfig(configFilepath)
+ const config = loadConfig(configFilepath, scriptsAndArgs)
if (!config) {
process.exit(FAIL_CODE)
}
diff --git a/yarn.lock b/yarn.lock
index ceea17e..68280a9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -90,8 +90,8 @@ align-text@^0.1.1, align-text@^0.1.3:
repeat-string "^1.5.2"
all-contributors-cli@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-3.1.0.tgz#1575e0e7815a933b9fa743b0e214e8c1406beedc"
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-3.1.1.tgz#ef3446020d06d1c85a11d045f7a788b87c9f46e4"
dependencies:
async "^2.0.0-rc.1"
inquirer "^0.12.0"
@@ -137,8 +137,8 @@ append-transform@^0.4.0:
default-require-extensions "^1.0.0"
aproba@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.0.4.tgz#2713680775e7614c8ba186c065d4e2e52d1072c0"
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.0.tgz#4d8f047a318604e18e3c06a0e52230d3d19f147b"
are-we-there-yet@~1.0.0:
version "1.0.6"
@@ -272,8 +272,8 @@ aws-sign2@~0.6.0:
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
aws4@^1.2.1:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755"
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
babel-cli@^6.22.2:
version "6.22.2"
@@ -954,8 +954,8 @@ braces@^1.8.2:
repeat-element "^1.1.2"
brorand@^1.0.1:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.0.6.tgz#4028706b915f91f7b349a2e0bf3c376039d216e5"
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.0.7.tgz#6677fa5e4901bdbf9c9ec2a748e28dca407a9bfc"
browser-resolve@^1.11.2:
version "1.11.2"
@@ -1572,8 +1572,8 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
elliptic@^6.0.0:
- version "6.3.2"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.2.tgz#e4c81e0829cf0a65ab70e998b8232723b5c1bc48"
+ version "6.3.3"
+ resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f"
dependencies:
bn.js "^4.4.0"
brorand "^1.0.1"
@@ -1834,6 +1834,10 @@ esprima@^2.6.0, esprima@^2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
+esprima@^3.1.1:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+
esprima@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.0.0.tgz#53cf247acda77313e551c3aa2e73342d3fb4f7d9"
@@ -2169,8 +2173,8 @@ gauge@~1.2.0:
lodash.padstart "^4.1.0"
gauge@~2.7.1:
- version "2.7.2"
- resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.2.tgz#15cecc31b02d05345a5d6b0e171cdb3ad2307774"
+ version "2.7.3"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09"
dependencies:
aproba "^1.0.3"
console-control-strings "^1.0.0"
@@ -2179,7 +2183,6 @@ gauge@~2.7.1:
signal-exit "^3.0.0"
string-width "^1.0.1"
strip-ansi "^3.0.1"
- supports-color "^0.2.0"
wide-align "^1.1.0"
generate-function@^2.0.0:
@@ -2960,13 +2963,20 @@ js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
-js-yaml@3.7.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
+js-yaml@3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80"
dependencies:
argparse "^1.0.7"
esprima "^2.6.0"
+js-yaml@^3.5.1, js-yaml@^3.7.0:
+ version "3.8.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.1.tgz#782ba50200be7b9e5a8537001b7804db3ad02628"
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^3.1.1"
+
jsbn@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.0.tgz#650987da0dd74f4ebf5a11377a2aa2d273e97dfd"
@@ -4131,7 +4141,7 @@ regenerator-transform@0.9.8:
regex-cache@^0.4.2:
version "0.4.3"
- resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
+ resolved "http://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145"
dependencies:
is-equal-shallow "^0.1.3"
is-primitive "^2.0.0"
@@ -4358,8 +4368,8 @@ sane@~1.4.1:
watch "~0.10.0"
sax@^1.2.1:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
semantic-release@^6.3.6:
version "6.3.6"
@@ -4652,10 +4662,6 @@ strip-json-comments@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
-supports-color@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a"
-
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@@ -4819,6 +4825,10 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
+type-detect@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.0.tgz#62053883542a321f2f7b25746dc696478b18ff6b"
+
typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"