Skip to content

Commit

Permalink
Merge pull request #69 from thoov/shareable-scripts
Browse files Browse the repository at this point in the history
Add support for shareable modules
  • Loading branch information
searls authored Oct 10, 2018
2 parents cad15b0 + b01719f commit 9db01a0
Show file tree
Hide file tree
Showing 20 changed files with 283 additions and 35 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,42 @@ You can configure either or both of `"path"` and `"windowsPath"` to custom
locations of your choosing. This may be handy in situations where multiple
projects share the same set of scripts.

### Sharing scripts via node modules

You can configure scripty to include certain node modules into its executable
search space. This is beneficial if you would like to create a centralized place
for your scripts and then share them across multiple projects. To include modules
add a `"scripty"` object property, `modules`, to your package.json like so:

``` json
"scripty": {
"modules": ["packageA", "packageB"]
}
```

Each node module must contain a `scripts` directory. Below is an example directory
structure:

```
root/
scripts/
foo
node_modules/
packageA/
scripts/
foo
bar
packageB/
scripts/
bar
baz
```

In the above example the resolution of `foo` would resolve to `root.scripts.foo`. Local scripts
take priority over ones defined in modules. The resolution of `bar` would resolve to
`root.node_modules.packageA.scripts.bar` as packageA was the first module defined
in the `scripty.modules` config.

### Dry runs

To perform a dry run of your scripts—something that's handy to check which
Expand Down
1 change: 1 addition & 0 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ if (!lifecycleEvent) {
stdio: 'inherit'
},
resolve: {
modules: loadOption('modules'),
scripts: loadOption('path'),
scriptsWin: loadOption('windowsPath')
}
Expand Down
52 changes: 44 additions & 8 deletions lib/load-option.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
var _ = require('lodash')

module.exports = function loadOption (name) {
var env = process.env['SCRIPTY_' + _.snakeCase(name).toUpperCase()]
if (envVarSet(posixEnvVarName(name))) {
return boolEnvVarValue(posixEnvVarName(name))
} else if (envVarSet(packageEnvVarName(name))) {
return boolEnvVarValue(packageEnvVarName(name))
} else if (envVarSet(packageArrayEnvVarName(name))) {
return arrayEnvVarValue(packageEnvVarName(name))
}
}

function boolEnvVarValue (envVarName) {
var value = process.env[envVarName]

if (value === 'true') {
return true
} else if (value === 'false') {
return false
} else {
return value
}
}

function arrayEnvVarValue (envVarName) {
var count = 0
var result = []

while (envVarSet(envVarName + '_' + count)) {
result.push(process.env[envVarName + '_' + count])
count++
}

if (env === 'true') return true
if (env === 'false') return false
if (env) return env
return result
}

function envVarSet (envVarName) {
return !!process.env[envVarName]
}

var pkg = process.env['npm_package_scripty_' + name]
function posixEnvVarName (optionName) {
return 'SCRIPTY_' + _.snakeCase(optionName).toUpperCase()
}

function packageEnvVarName (optionName) {
return 'npm_package_scripty_' + optionName
}

if (pkg === 'true') return true
if (pkg === 'false') return false
if (pkg) return pkg
function packageArrayEnvVarName (optionName) {
return packageEnvVarName(optionName) + '_0'
}
7 changes: 7 additions & 0 deletions lib/load-option.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,12 @@ module.exports = {
process.env.npm_package_scripty_testKey = 'wrong value'

assert.equal(subject('testKey'), 'right value')
},
packageArray: function () {
process.env.npm_package_scripty_testKey_0 = 'value 1'
process.env.npm_package_scripty_testKey_1 = 'value 2'
process.env.npm_package_scripty_testKey_2 = 'value 3'

assert.deepEqual(subject('testKey'), ['value 1', 'value 2', 'value 3'])
}
}
22 changes: 15 additions & 7 deletions lib/resolve-script/glob-patterns.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
var path = require('path')

module.exports = function (dir1, dir2) {
var expanded = path.resolve(dir1, path.join.apply(this, dir2.split(':')))
module.exports = function (dir1, dir2, moduleDirs) {
var globs = []
var scriptPath = path.join.apply(this, dir2.split(':'))
var searchSpaceDirs = [].concat(dir1, moduleDirs)

searchSpaceDirs.forEach(function (dir) {
var scriptDir = path.resolve(dir, scriptPath)

return [
// exact file match (+ any same-named extensions)
expanded + '+(|.*)',
globs.push(scriptDir + '+(|.*)')

// a nested index file match (+ any same-named extensions)
path.join(expanded, 'index+(|.*)'),
globs.push(path.join(scriptDir, 'index+(|.*)'))

// any nested files at all
path.join(expanded, '*')
]
globs.push(path.join(scriptDir, '*'))
})

return globs
}
27 changes: 25 additions & 2 deletions lib/resolve-script/glob-patterns.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
var path = require('path')

var subject = require('./glob-patterns')

module.exports = {
simpleCase: function () {
var result = subject(__dirname, 'foo:bar')
var result = subject(__dirname, 'foo:bar', [])

assert.equal(result.length, 3)
assert.equal(result[0], path.resolve(__dirname, 'foo/bar') + '+(|.*)')
assert.equal(result[1], path.resolve(__dirname, 'foo/bar/index') + '+(|.*)')
assert.equal(result[2], path.resolve(__dirname, 'foo/bar/*'))
},
modulesCase: function () {
var result = subject(
__dirname,
'foo:bar',
[
path.join(process.cwd(), 'node_modules/bar/scripts'),
path.join(process.cwd(), 'node_modules/baz/scripts')
]
)

assert.equal(result.length, 9)
assert.equal(result[0], path.resolve(__dirname, 'foo/bar') + '+(|.*)')
assert.equal(result[1], path.resolve(__dirname, 'foo/bar/index') + '+(|.*)')
assert.equal(result[2], path.resolve(__dirname, 'foo/bar/*'))

assert.equal(result[3], path.resolve(process.cwd(), 'node_modules/bar/scripts/foo/bar') + '+(|.*)')
assert.equal(result[4], path.resolve(process.cwd(), 'node_modules/bar/scripts/foo/bar/index') + '+(|.*)')
assert.equal(result[5], path.resolve(process.cwd(), 'node_modules/bar/scripts/foo/bar/*'))

assert.equal(result[6], path.resolve(process.cwd(), 'node_modules/baz/scripts/foo/bar') + '+(|.*)')
assert.equal(result[7], path.resolve(process.cwd(), 'node_modules/baz/scripts/foo/bar/index') + '+(|.*)')
assert.equal(result[8], path.resolve(process.cwd(), 'node_modules/baz/scripts/foo/bar/*'))
}
}
4 changes: 2 additions & 2 deletions lib/resolve-script/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ var scriptDirs = require('./script-dirs')

module.exports = function (name, options, cb) {
var dirs = scriptDirs(options)
var userGlob = globPatterns(dirs.userDir, name)
var userGlob = globPatterns(dirs.userDir, name, dirs.moduleDirs)
findExecutables(userGlob, function (er, userPaths) {
if (userPaths.length > 0) {
cb(er, userPaths)
} else {
var ourGlob = globPatterns(dirs.ourDir, name)
var ourGlob = globPatterns(dirs.ourDir, name, [])
findExecutables(ourGlob, function (er, ourPaths) {
if (ourPaths.length > 0) {
cb(er, ourPaths)
Expand Down
9 changes: 6 additions & 3 deletions lib/resolve-script/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ module.exports = {
this.scriptDirs = td.replace('./script-dirs')
this.subject = require('./index')

td.when(this.scriptDirs(OPTIONS)).thenReturn({userDir: 'A', ourDir: 'B'})
td.when(this.globPatterns('A', 'fake')).thenReturn(['glob1'])
td.when(this.globPatterns('B', 'fake')).thenReturn(['glob2'])
td.when(this.scriptDirs(OPTIONS)).thenReturn({userDir: 'A', ourDir: 'B', moduleDirs: []})
td.when(this.globPatterns('A', 'fake', [])).thenReturn(['glob1'])
td.when(this.globPatterns('B', 'fake', [])).thenReturn(['glob2'])

td.when(this.globPatterns('A', 'fake', [])).thenReturn(['glob1'])
td.when(this.globPatterns('B', 'fake', [])).thenReturn(['glob2'])
},
bothUserAndBuiltInScriptsExist: function (done) {
td.when(this.findExecutables(['glob1'])).thenCallback(null, ['user-path'])
Expand Down
18 changes: 18 additions & 0 deletions lib/resolve-script/script-dirs.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
var path = require('path')
var fs = require('fs')
var lodash = require('lodash')
var resolvePkg = require('resolve-pkg')

module.exports = function (options, platform) {
platform = platform || process.platform
var modules = lodash.get(options, 'modules', [])
return {
userDir: find(process.cwd(), options, 'scripts', platform),
moduleDirs: findModulePaths(modules).map(function (path) { return find(path, options, 'scripts', platform) }),
ourDir: find(path.resolve(__dirname, '../..'), options, 'builtIn', platform)
}
}
Expand All @@ -20,3 +24,17 @@ function find (base, options, key, platform) {
return path.resolve(base, 'scripts')
}
}

function findModulePaths (modules) {
var modulePaths = []

modules.forEach(function (moduleName) {
var modulePath = resolvePkg(moduleName)

if (modulePath) {
modulePaths.push(modulePath)
}
})

return modulePaths
}
37 changes: 25 additions & 12 deletions lib/resolve-script/script-dirs.test.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
var path = require('path')

var subject = require('./script-dirs')

module.exports = {
beforeEach: function () {
this.resolvePkg = td.replace('resolve-pkg')
this.subject = require('./script-dirs')
},
unixUserScripts: function () {
assert.equal(subject({scripts: 'A', scriptsWin: 'B'}, 'lolnix').userDir, 'A')
assert.equal(subject({scripts: null, scriptsWin: 'B'}, 'lolnix').userDir,
assert.equal(this.subject({scripts: 'A', scriptsWin: 'B'}, 'lolnix').userDir, 'A')
assert.equal(this.subject({scripts: null, scriptsWin: 'B'}, 'lolnix').userDir,
path.resolve(process.cwd(), 'scripts'))
},
unixBuiltInScripts: function () {
assert.equal(subject({builtIn: 'A', builtInWin: 'B'}, 'lolnix').ourDir, 'A')
assert.equal(subject({builtIn: null, builtInWin: 'B'}, 'lolnix').ourDir,
assert.equal(this.subject({builtIn: 'A', builtInWin: 'B'}, 'lolnix').ourDir, 'A')
assert.equal(this.subject({builtIn: null, builtInWin: 'B'}, 'lolnix').ourDir,
path.resolve(__dirname, '../../scripts'))
},
windowsUserScripts: function () {
assert.equal(subject({scripts: 'A', scriptsWin: 'B'}, 'win32').userDir, 'B')
assert.equal(subject({scripts: 'A', scriptsWin: null}, 'win32').userDir,
assert.equal(this.subject({scripts: 'A', scriptsWin: 'B'}, 'win32').userDir, 'B')
assert.equal(this.subject({scripts: 'A', scriptsWin: null}, 'win32').userDir,
path.resolve(process.cwd(), 'scripts-win'))
// change dirs to any place that lacks a ./scripts-win dir
td.when(td.replace(process, 'cwd')()).thenReturn(__dirname)
assert.equal(subject({scripts: 'A', scriptsWin: null}, 'win32').userDir, 'A')
assert.equal(subject({scripts: null, scriptsWin: null}, 'win32').userDir,
assert.equal(this.subject({scripts: 'A', scriptsWin: null}, 'win32').userDir, 'A')
assert.equal(this.subject({scripts: null, scriptsWin: null}, 'win32').userDir,
path.resolve(process.cwd(), 'scripts'))
},
windowsBuiltIn: function () {
assert.equal(subject({builtIn: 'A', builtInWin: 'B'}, 'win32').ourDir, 'B')
assert.equal(subject({builtIn: 'A', builtInWin: null}, 'win32').ourDir,
assert.equal(this.subject({builtIn: 'A', builtInWin: 'B'}, 'win32').ourDir, 'B')
assert.equal(this.subject({builtIn: 'A', builtInWin: null}, 'win32').ourDir,
path.resolve(__dirname, '../../scripts-win'))
},
moduleDirs: function () {
td.when(this.resolvePkg('bar')).thenReturn('node_modules/bar')

assert.deepEqual(
this.subject({
modules: ['bar']
},
'lolnix'
).moduleDirs, [path.resolve(process.cwd(), 'node_modules/bar/scripts')]
)
}
}
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"dependencies": {
"async": "^2.6.1",
"glob": "^7.0.3",
"lodash": "^4.8.2"
"lodash": "^4.8.2",
"resolve-pkg": "^1.0.0"
},
"standard": {
"globals": [
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/modules/node_modules/foo/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/fixtures/modules/node_modules/foo/scripts-win/foo.cmd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/fixtures/modules/node_modules/foo/scripts-win/user.cmd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions test/fixtures/modules/node_modules/foo/scripts/foo

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions test/fixtures/modules/node_modules/foo/scripts/user

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions test/fixtures/modules/scripts-win/user.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@ECHO OFF

echo Hello, World! from user win

5 changes: 5 additions & 0 deletions test/fixtures/modules/scripts/user
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

WORLD="World"

echo "Hello, $WORLD! from user"
Loading

0 comments on commit 9db01a0

Please sign in to comment.