Skip to content

Commit

Permalink
feat(require): The module you require can now be an ES6 module
Browse files Browse the repository at this point in the history
That's mostly just a side-effect of the clean-up I've been doing.
  • Loading branch information
Kent C. Dodds committed May 20, 2016
1 parent 3ecdaf1 commit efb2645
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 98 deletions.
93 changes: 0 additions & 93 deletions bin/p-s.js

This file was deleted.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"scripts": {
"start": "p-s",
"test": "p-s test",
"localstart": "npm start build && ./bin/p-s.js"
"localstart": "npm start build && ./dist/bin/p-s.js"
},
"bin": {
"p-s": "./bin/p-s.js",
"package-scripts": "./bin/p-s.js"
"p-s": "./dist/bin/p-s.js",
"package-scripts": "./dist/bin/p-s.js"
},
"files": [
"bin",
Expand Down
69 changes: 68 additions & 1 deletion src/bin-utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
import {resolve} from 'path'
import remove from 'lodash.remove'
import contains from 'lodash.contains'
import isPlainObject from 'lodash.isplainobject'
import shellEscape from 'shell-escape'
import isEmpty from 'lodash.isempty'
import colors from 'colors/safe'

import getLogger from './get-logger'
import {resolveScriptObjectToScript} from './resolve-script-object-to-string'

export {getScriptsAndArgs, help}
const log = getLogger()

/**
* Attempts to load the given module. This is used for the --require functionality of the CLI
* @param {String} moduleName The module to attempt to require
* @return {*} The required module
*/
const preloadModule = getAttemptModuleRequireFn((moduleName, requirePath) => {
log.warn({
message: colors.yellow(`Unable to preload "${moduleName}". Attempted to require as "${requirePath}"`),
ref: 'unable-to-preload-module',
})
return undefined
})

/**
* 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
*/
const loadConfig = getAttemptModuleRequireFn(function onFail(configPath, requirePath) {
log.error({
message: colors.red(`Unable to find config at "${configPath}". Attempted to require as "${requirePath}"`),
ref: 'unable-to-find-config',
})
return undefined
})

export {getScriptsAndArgs, help, getModuleRequirePath, preloadModule, loadConfig}


/****** implementations ******/

function getScriptsAndArgs(program) {
let scripts, args, parallel
Expand Down Expand Up @@ -36,6 +69,40 @@ function getArgs(args, rawArgs, scripts) {
return shellEscape(cleanedArgs)
}

/**
* Determines the proper require path for a module. If the path starts with `.` then it is resolved with process.cwd()
* @param {String} moduleName The module path
* @return {String} the module path to require
*/
function getModuleRequirePath(moduleName) {
return moduleName[0] === '.' ? resolve(process.cwd(), moduleName) : moduleName
}

function getAttemptModuleRequireFn(onFail) {
return function attemptModuleRequire(moduleName) {
const requirePath = getModuleRequirePath(moduleName)
try {
return requireDefaultFromModule(requirePath)
} catch (e) {
return onFail(moduleName, requirePath)
}
}
}

/**
* Requires the given module and returns the `default` if it's an `__esModule`
* @param {String} modulePath The module to require
* @return {*} The required module (or it's `default` if it's an `__esModule`)
*/
function requireDefaultFromModule(modulePath) {
const mod = require(modulePath)
if (mod.__esModule) {
return mod.default
} else {
return mod
}
}

function help({scripts}) {
const availableScripts = getAvailableScripts(scripts)
const scriptLines = availableScripts.map(({name, description, script}) => {
Expand Down
60 changes: 59 additions & 1 deletion src/bin-utils.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import test from 'ava'
import {resolve} from 'path'
import colors from 'colors/safe'
import {getScriptsAndArgs, help} from './bin-utils'
import {spy} from 'sinon'
import proxyquire from 'proxyquire'
import {getScriptsAndArgs, help, preloadModule, loadConfig} from './bin-utils'

proxyquire.noCallThru()

test('getScriptsAndArgs: gets scripts', t => {
const {scripts} = getScriptsAndArgs({
Expand All @@ -27,6 +32,59 @@ test('getScriptsAndArgs: passes args to scripts', t => {
t.is(args, '--watch --verbose')
})

test('preloadModule: resolves a relative path', t => {
const relativePath = '../test/fixtures/my-module'
const val = preloadModule(relativePath)
t.is(val, 'hello')
})

test('preloadModule: resolves an absolute path', t => {
const relativePath = '../test/fixtures/my-module'
const absolutePath = resolve(__dirname, relativePath)
const val = preloadModule(absolutePath)
t.is(val, 'hello')
})

test('preloadModule: resolves a node_module', t => {
const val = preloadModule('colors/safe')
t.is(val, colors)
})

test('preloadModule: logs a warning when the module cannot be required', t => {
const warn = spy()
const proxiedPreloadModule = proxyquire('./bin-utils', {
'./get-logger': () => ({warn}),
}).preloadModule
const val = proxiedPreloadModule('./module-that-does-exist')
t.is(val, undefined)
t.true(warn.calledOnce)
const [{message}] = warn.firstCall.args
t.regex(message, /Unable to preload "\.\/module-that-does-exist\"/)
})

test('loadConfig: logs a warning when the module cannot be required', t => {
const error = spy()
const proxiedReloadConfig = proxyquire('./bin-utils', {
'./get-logger': () => ({error}),
}).loadConfig
const val = proxiedReloadConfig('./config-that-does-exist')
t.is(val, undefined)
t.true(error.calledOnce)
const [{message}] = error.firstCall.args
t.regex(message, /Unable to find config at "\.\/config-that-does-exist\"/)
})

test('loadConfig: can load ES6 module', t => {
const relativePath = '../test/fixtures/fake-es6-module'
const val = loadConfig(relativePath)
t.deepEqual(val, {
scripts: {
skywalker: `echo "That's impossible!!"`,
},
options: {},
})
})

test('help: formats a nice message', t => {
const config = {
scripts: {
Expand Down
63 changes: 63 additions & 0 deletions src/bin/p-s.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env node
import findUp from 'find-up'
import merge from 'lodash.merge'
import program from 'commander'
import runPackageScript from '../index'
import {getScriptsAndArgs, help, preloadModule, loadConfig} from '../bin-utils'
import getLogger from '../get-logger'
const log = getLogger()
const FAIL_CODE = 1

program
.version(require('../../package.json').version)
.allowUnknownOption()
.option('-s, --silent', 'Silent p-s output')
.option('-p, --parallel <script-name1,script-name2>', 'Scripts to run in parallel (comma seprated)')
.option('-c, --config <filepath>', 'Config file to use (defaults to nearest package-scripts.js)')
.option('-l, --log-level <level>', 'The log level to use (error, warn, info [default])')
.option('-r, --require <module>', 'Module to preload')
.on('--help', onHelp)
.parse(process.argv)

if (process.argv.length < 3) {
program.outputHelp()
} else {
loadAndRun()
}

function loadAndRun() {
const scriptsAndArgs = getScriptsAndArgs(program)
const psConfig = getPSConfig()

runPackageScript({
scriptConfig: psConfig.scripts,
scripts: scriptsAndArgs.scripts,
args: scriptsAndArgs.args,
options: merge(psConfig.options, {
silent: program.silent,
parallel: scriptsAndArgs.parallel,
logLevel: program.logLevel,
}),
}, result => {
if (result.error) {
log.error(result.error)
process.exit(FAIL_CODE)
}
process.exit(result.code)
})
}

function getPSConfig() {
if (program.require) {
preloadModule(program.require)
}
const config = loadConfig(program.config || findUp.sync('package-scripts.js'))
if (!config) {
process.exit(FAIL_CODE)
}
return config
}

function onHelp() {
log.info(help(getPSConfig()))
}
9 changes: 9 additions & 0 deletions test/fixtures/fake-es6-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
__esModule: true,
default: {
scripts: {
skywalker: `echo "That's impossible!!"`,
},
options: {},
},
}
1 change: 1 addition & 0 deletions test/fixtures/my-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = 'hello'

0 comments on commit efb2645

Please sign in to comment.