├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── TODO.md
├── doc
├── npm-launch-package.json.gif
└── npm-launch.gif
├── launch.scripts.json5
├── lib
├── cli.js
├── index.js
├── loader.js
├── loaders
│ ├── _babel.js
│ ├── js.js
│ ├── json.js
│ └── test
│ │ ├── js.test.js
│ │ └── json.test.js
├── task
│ ├── classes.js
│ ├── index.js
│ └── registry.js
└── util.js
├── package.json
└── test
├── fixtures
├── launch.scripts.js
└── launch.scripts.json5
└── functional.test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .nyc_output/
2 | coverage/
3 | node_modules/
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 6
4 | - 5
5 | - 4
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # npm-launch - Changelog
2 |
3 | This is the brief changelog. For more details see https://github.com/andywer/npm-launch/releases.
4 |
5 | ## v0.3.0
6 |
7 | - Show last (non-empty) line of command output: npm-launch/issues#5
8 | - Minor fix: Prevent unhelpful stack trace: npm-launch/issues#6
9 | - Minor fix: Set process title to `npm-launch` (defaulted to `node` until now)
10 | - Minor fix: Add `engines` prop to `package.json`
11 |
12 | ## v0.2.0
13 |
14 | - Support for running tasks concurrently using `run-p`/`run-parallel`
15 |
16 | ## v0.1.4
17 |
18 | - CLI option: `--list`
19 |
20 | ## v0.1.3
21 |
22 | - Bug fix: Babel transformation fix (concerns JS launch files only, not JSON)
23 |
24 | ## v0.1.2
25 |
26 | - Add unit and functional tests
27 | - Bug fix: Exit with status code 1 if there is an error
28 |
29 | ## v0.1.1
30 |
31 | - Show the currently run shell command for JSON tasks
32 |
33 | ## v0.1.0
34 |
35 | - Initial release. Yeah!
36 | - Supports JSON5 and ES6 modules
37 | - Uses Listr
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Andy Wermke
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 npm-launch
2 |
3 | [](https://travis-ci.org/andywer/npm-launch)
4 | [](http://standardjs.com/)
5 | [](https://www.npmjs.com/package/npm-launch)
6 |
7 | Minimalistic task runner on steroids. Write scripts using JSON5 or ES6 JavaScript modules.
8 |
9 |
10 |
11 |
12 |
13 | - Small, fast, supports JSON5 and JS code
14 | - Clean and tidy: Show command's output only in case of error
15 | - Fully compatible with package.json `scripts`: You can just copy & paste them to the launch file
16 | - Runs locally installed commands from `node_modules/.bin` like package.json `scripts`
17 | - Uses [Listr](https://github.com/SamVerschueren/listr) to provide beautiful console output
18 | - Runs on node.js 4+
19 |
20 |
21 | ## Installation
22 |
23 | ```sh
24 | npm install --save-dev npm-launch
25 | ```
26 |
27 | ## Why?
28 |
29 | - Because `scripts` in package.json are a mess
30 | - When they fail, `npm` prints ~25 lines of non-helpful disclaimers
31 | - If a script called by another script fails, you already got 50 lines to scroll
32 | - `gulp` & `grunt` instead need 100 lines of code for things that take less than five lines on the command line
33 | - Your terminal is constantly flooded with output everytime you run a task
34 |
35 | But no more! Let's take the sample launch file from the screencast before and use
36 | it in our `package.json`:
37 |
38 |
39 |
40 |
41 |
42 | Here we go, everything is clean and concise now!
43 |
44 | **A clean and short package.json. Commented tasks. Concurrency support out-of-the-box.**
45 |
46 |
47 | ## Usage (JSON)
48 |
49 | ```js
50 | // File: launch.scripts.json5
51 | {
52 | build: "webpack -c webpack-config.production.js",
53 | test: "run-parallel lint mocha",
54 |
55 | //////////
56 | // Hooks:
57 |
58 | prepush: "run build && run test",
59 |
60 | //////////////////
61 | // Testing tasks:
62 |
63 | lint: "standard lib/**/*.js",
64 | mocha: "mocha"
65 | }
66 | ```
67 |
68 | ```sh
69 | $ launch build test # run tasks "build" and "test"
70 | # or
71 | $ launch -f path/to/launch-file build test
72 | ```
73 |
74 | Features:
75 | - Comments in JSON
76 | - Run tasks concurrently by using `run-parallel ...` (or short `run-p`)
77 | - Nicer syntax, easy to read and write
78 | - Very short and concise
79 | - Fully compatible with standard JSON
80 |
81 |
82 | ## Usage (ES6 module)
83 |
84 | ```js
85 | // File: launch.scripts.js
86 |
87 | const shell = module.parent.exports.shell
88 | const delay = 1500
89 |
90 | // Define a task "npmPruneList"
91 | export async function npmPruneList () {
92 | // Execute `npm prune`
93 | await shell('npm prune')
94 | // Then execute `npm list`
95 | await shell('npm list')
96 | }
97 |
98 | // Define a task "wasteSomeTime"
99 | export function wasteSomeTime () {
100 | return new Promise((resolve) => {
101 | setTimeout(() => resolve(), delay)
102 | })
103 | }
104 |
105 | // Define a task "default" that will just run "npmPruneList" & "wasteSomeTime" sequentially
106 | export default [ npmPruneList, wasteSomeTime ]
107 | ```
108 |
109 | Features:
110 | - Completely transparent API: No need to import anything, just export
111 | - ES6 module syntax is available out-of-the-box
112 | - `async`/`await` are available out-of-the-box
113 | - The additional power you need to solve complex tasks
114 |
115 |
116 | ## CLI
117 |
118 | Run the tasks `build` and `test`:
119 | ```sh
120 | launch build test
121 | ```
122 |
123 | Run the `default` task:
124 | ```sh
125 | launch
126 | ```
127 |
128 | List all available tasks:
129 | ```sh
130 | launch --list
131 | ```
132 |
133 | Print CLI usage help text:
134 | ```sh
135 | launch --help
136 | ```
137 |
138 |
139 | ## Tips
140 |
141 | - If you do not provide a filename for a launch file it will look for a file named `launch.scripts.js`/`launch.scripts.json`/`launch.scripts.json5`
142 | - Auto-camelcasing: Instead of `$ launch myTask` you can also run `$ launch my-task`
143 |
144 |
145 | ## Minor known limitations
146 |
147 | These limitations only apply if your launch file contains custom JS code rather than JSON tasks.
148 |
149 | #### No checkmark list for tasks being called by code
150 |
151 | Tasks called by code are tracked and displayed, but not as a checkmark list,
152 | but just as a hint which one is currently run. It's simple: In this case we
153 | cannot create a list of subtasks beforehand, since there is no way to know
154 | which sub-tasks the function is going to call.
155 |
156 | #### console.log() in tasks may disturb the output
157 |
158 | If you call `console.log()` (or similar) in your launch file then the checkmark
159 | list will probably be corrupted.
160 |
161 |
162 | ## Changelog
163 |
164 | See [CHANGELOG.md](./CHANGELOG.md) or [Releases Page](https://github.com/andywer/npm-launch/releases) for more details.
165 |
166 |
167 | ## License
168 |
169 | This library is released under the terms of the MIT license. See [LICENSE](./LICENSE) for details.
170 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | - CLI option to not use Listr and write all console output to terminal
4 | (--plain-output || !require('tty').isatty(process.stdout))
5 | (see https://github.com/SamVerschueren/listr/issues/11)
6 | - Come up with some way to let tasks print a tail of n lines to terminal when all is done
7 | (Maybe just parse `| tail -n ` at the end of commands)
8 | - Fix: `babel-register` is currently run with `{ babelrc: false }`, since otherwise a local `.babelrc` would override our babel plugins config
9 | - Implement a way to run stuff in parallel in JSON
10 | - Show full stack traces on errors thrown by npm-launch itself only if a `verbose` flag is set
11 | - Run JS code in sub process to prevent stdout/stderr to be written to unfiltered (breaks Listr output)
12 | - Maybe: Allow methods to return an array of task names to execute
13 |
--------------------------------------------------------------------------------
/doc/npm-launch-package.json.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andywer/npm-launch/a192fe3696cba23c14854d43a1550f81f1dff356/doc/npm-launch-package.json.gif
--------------------------------------------------------------------------------
/doc/npm-launch.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andywer/npm-launch/a192fe3696cba23c14854d43a1550f81f1dff356/doc/npm-launch.gif
--------------------------------------------------------------------------------
/launch.scripts.json5:
--------------------------------------------------------------------------------
1 | {
2 | test: "run-p ava lint",
3 |
4 | ava: "ava {lib,test}/**/*.test.js",
5 | lint: "standard lib/*.js lib/**/*.js"
6 | }
7 |
--------------------------------------------------------------------------------
/lib/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict'
3 |
4 | const colors = require('colors')
5 | const EventEmitter = require('events')
6 | const fs = require('fs')
7 |
8 | const argv = require('yargs')
9 | .usage('Usage: $0 [ ...] []')
10 | .example('$0', 'Runs task "default" from file "launch.scripts.{js,json,json5}".')
11 | .example('$0 build test', 'Runs task "build" and then "test".')
12 | .example('$0 -f filename.js foo', 'Runs task "foo" of task file "filename.js".')
13 | .example('$0 --list', 'Lists all available tasks.')
14 | .describe('f', 'Specify input file')
15 | .nargs('f', 1)
16 | .alias('f', 'file')
17 | .describe('list', 'List all available tasks')
18 | .describe('no-color', 'Prevents output from being colored') // require('colors') will check for --no-color
19 | .help()
20 | .alias('h', 'help')
21 | .version()
22 | .argv
23 |
24 | const loader = require('./loader')
25 | const taskRunner = require('./index')
26 |
27 | const DEFAULT_INPUT_FILENAMES = [ 'launch.scripts.js', 'launch.scripts.json', 'launch.scripts.json5' ]
28 |
29 | process.title = 'npm-launch'
30 |
31 | try {
32 | main(argv)
33 | } catch (error) {
34 | console.error(error.stack)
35 | }
36 |
37 | function main (args) {
38 | const taskFile = args.file ? checkTasksFile(args.file) : locateTasksFile()
39 | const tasksToRun = args._.length > 0 ? args._ : [ 'default' ]
40 |
41 | // This emitter will be used to inform subscribers about calls on methods
42 | // if the taskFile is an ES6 module (JS code)
43 | const taskCallEmitter = new EventEmitter()
44 |
45 | const tasks = loader.loadTasksFromFile(taskFile, taskCallEmitter)
46 | beforeRunningTasks(args, tasks, taskFile)
47 | runTasks(tasks, taskCallEmitter, tasksToRun)
48 | }
49 |
50 | function checkTasksFile (filePath) {
51 | if (!fileExists(filePath)) {
52 | throw new Error(`File cannot be found: ${filePath}`)
53 | }
54 | return filePath
55 | }
56 |
57 | function locateTasksFile (filePath) {
58 | const located = DEFAULT_INPUT_FILENAMES.find((fileName) => fileExists(fileName))
59 |
60 | if (located) {
61 | return located
62 | } else {
63 | throw new Error('Non of the default task files found in current working directory: ' + DEFAULT_INPUT_FILENAMES.join(', '))
64 | }
65 | }
66 |
67 | function beforeRunningTasks (args, tasks, taskFile) {
68 | if (args.list) {
69 | printAvailableTasks(tasks, taskFile)
70 | process.exit(0)
71 | }
72 | }
73 |
74 | function printAvailableTasks (tasks, taskFile) {
75 | const taskNames = Object.keys(tasks)
76 |
77 | console.log(`Tasks in ${taskFile}:`)
78 | taskNames.sort().forEach((taskName) => console.log(` ${taskName}`))
79 | }
80 |
81 | function runTasks (allTasks, taskCallEmitter, taskNamesToRun) {
82 | return taskRunner
83 | .runTasks(allTasks, taskNamesToRun, taskCallEmitter)
84 | .catch((error) => {
85 | if (error instanceof Error && 'stdout' in error && 'stderr' in error) {
86 | console.error(colors.red(error.message))
87 | } else if (error instanceof Error) {
88 | console.error(colors.red(error.stack))
89 | } else {
90 | console.error(colors.red(error))
91 | }
92 | process.exit(1)
93 | })
94 | }
95 |
96 | function fileExists (filePath) {
97 | let stat
98 | try {
99 | stat = fs.statSync(filePath)
100 | } catch (error) {
101 | return false
102 | }
103 | return stat.isFile()
104 | }
105 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | const camelcase = require('camelcase')
2 | const Observable = require('zen-observable')
3 | const createTaskFork = require('./task').createFork
4 |
5 | /**
6 | * @param {object} allTasks { : }
7 | * @param {Array} taskNamesToRun
8 | * @param {EventEmitter} taskCallEmitter
9 | * @return {Promise}
10 | */
11 | function runTasks (allTasks, taskNamesToRun, taskCallEmitter) {
12 | const tasksToRun = selectTasks(allTasks, taskNamesToRun)
13 |
14 | const taskCalls = new Observable((observer) => {
15 | taskCallEmitter.on('call', (taskName) => observer.next(taskName))
16 | })
17 |
18 | const rootTask = createTaskFork('$root', tasksToRun)
19 | const listrRoot = rootTask.createListrTask(taskCalls).task()
20 |
21 | return listrRoot.run()
22 | }
23 |
24 | function selectTasks (tasks, taskNamesToRun) {
25 | const filteredTasks = []
26 |
27 | taskNamesToRun.forEach((taskName) => {
28 | const camelCasedTaskName = camelcase(taskName)
29 |
30 | if (tasks[ taskName ]) {
31 | filteredTasks.push(tasks[ taskName ])
32 | } else if (tasks[ camelCasedTaskName ]) {
33 | filteredTasks.push(tasks[ camelCasedTaskName ])
34 | } else {
35 | throw new Error(`Task is not defined: ${taskName}`)
36 | }
37 | })
38 |
39 | return filteredTasks
40 | }
41 |
42 | exports.runTasks = runTasks
43 |
--------------------------------------------------------------------------------
/lib/loader.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | function loadTasksFromFile (filePath, taskCallEmitter) {
4 | // Moved the loaders into here, since especially the JS loader is pretty expensive
5 |
6 | if (filePath.match(/\.json5?$/i)) {
7 | const loadTasksFromJsonFile = require('./loaders/json').loadFile
8 | return loadTasksFromJsonFile(filePath, taskCallEmitter)
9 | } else {
10 | const loadTasksFromJsFile = require('./loaders/js').loadFile
11 | return loadTasksFromJsFile(filePath, taskCallEmitter)
12 | }
13 | }
14 |
15 | exports.loadTasksFromFile = loadTasksFromFile
16 |
--------------------------------------------------------------------------------
/lib/loaders/_babel.js:
--------------------------------------------------------------------------------
1 | exports.plugins = [
2 | 'rewire',
3 | 'transform-async-to-generator',
4 | 'transform-es2015-modules-commonjs'
5 | ]
6 |
--------------------------------------------------------------------------------
/lib/loaders/js.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const babelPlugins = require('./_babel').plugins
5 | const taskModule = require('../task')
6 | const util = require('../util')
7 |
8 | const createTaskFromMethod = taskModule.createFromMethod
9 | const createAnonymousCommandTask = taskModule.createAnonymousCommand
10 | const createTaskFork = taskModule.createFork
11 | const createTaskReference = taskModule.createReference
12 |
13 | require('babel-register')({
14 | babelrc: false,
15 | plugins: babelPlugins
16 | })
17 |
18 | function loadFile (filePath, taskCallEmitter) {
19 | if (filePath.charAt(0) !== '/' && filePath.charAt(0) !== '.') {
20 | filePath = path.join(process.cwd(), filePath)
21 | }
22 |
23 | const exports = require(filePath)
24 |
25 | observeExports(exports, taskCallEmitter)
26 | return createTasksForExports(exports)
27 | }
28 |
29 | function observeExports (exportedTasks, taskCallEmitter) {
30 | if (!exportedTasks.__Rewire__) {
31 | return exportedTasks
32 | }
33 |
34 | Object.keys(exportedTasks)
35 | .filter((taskName) => typeof exportedTasks[ taskName ] === 'function' && !taskName.match(/^__/))
36 | .forEach((taskName) => {
37 | const origMethod = exportedTasks[ taskName ]
38 |
39 | exportedTasks.__Rewire__(taskName, function () {
40 | taskCallEmitter.emit('call', taskName)
41 | return origMethod.apply(null, arguments)
42 | })
43 | })
44 | }
45 |
46 | function createTasksForExports (exportedTasks) {
47 | const tasks = {}
48 |
49 | Object.keys(exportedTasks)
50 | .filter((taskName) => typeof exportedTasks[ taskName ] === 'function' && !taskName.match(/^__/))
51 | .forEach((taskName) => {
52 | tasks[ taskName ] = createTaskFromMethod(taskName, exportedTasks[ taskName ])
53 | })
54 |
55 | Object.keys(exportedTasks)
56 | .filter((taskName) => Array.isArray(exportedTasks[ taskName ]))
57 | .forEach((taskName) => {
58 | const subTasks = exportedTasks[ taskName ].map((item) => createTaskReferenceForArrayItem(item, taskName))
59 | tasks[ taskName ] = createTaskFork(taskName, subTasks)
60 | })
61 |
62 | return tasks
63 | }
64 |
65 | function createTaskReferenceForArrayItem (item, arrayTaskName) {
66 | switch (typeof item) {
67 | case 'function':
68 | return createAnonymousCommandTask(item.name, item)
69 | case 'string':
70 | return createTaskReference(item, arrayTaskName)
71 | default:
72 | throw new Error(`Tasks defined as array must include string and function items only. Check "${arrayTaskName}".`)
73 | }
74 | }
75 |
76 | exports.loadFile = loadFile
77 |
78 | // This export is supposed to be used by ES6 module input files:
79 | exports.shell = util.shell
80 |
--------------------------------------------------------------------------------
/lib/loaders/json.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const JSON5 = require('json5')
5 | const taskModule = require('../task')
6 | const util = require('../util')
7 | let shellObservable = util.shellObservable // must be `let`, since rewiring in unit tests would fail otherwise
8 |
9 | const createAnonymousCommandTask = taskModule.createAnonymousCommand
10 | const createTaskFork = taskModule.createFork
11 | const createAnonymousConcurrentTaskFork = taskModule.createAnonymousConcurrentTaskFork
12 | const createTaskReference = taskModule.createReference
13 |
14 | function loadFile (filePath) {
15 | const tasksJson = JSON5.parse(fs.readFileSync(filePath, { encoding: 'utf-8' }))
16 | const tasksResolved = {}
17 |
18 | Object.keys(tasksJson).forEach((taskName) => {
19 | tasksResolved[ taskName ] = resolveJsonTask(tasksJson[ taskName ], taskName, tasksResolved)
20 | })
21 |
22 | return tasksResolved
23 | }
24 |
25 | function resolveJsonTask (task, taskName, tasksResolved) {
26 | if (Array.isArray(task)) {
27 | const taskReferences = task.map((subTaskName) => createTaskReference(subTaskName, taskName))
28 | return createTaskFork(taskName, taskReferences)
29 | }
30 | if (typeof task !== 'string') {
31 | throw new Error(`Task must be a string or array. Invalid task given: ${taskName} (type ${typeof task})`)
32 | }
33 |
34 | return createTaskForTaskString(task, taskName)
35 | }
36 |
37 | function createTaskForTaskString (taskContent, taskName) {
38 | const subCommands = taskContent.split('&&').map((subCommand) => subCommand.trim())
39 | const subCommandTasks = subCommands.map((subCommand) => createTaskForSubCommand(subCommand, taskName))
40 |
41 | return createTaskFork(taskName, subCommandTasks)
42 | }
43 |
44 | function createTaskForSubCommand (subCommand, taskName) {
45 | const commandRun = /^run /i
46 | const commandRunParallel = /^(run-p|run-parallel) /i
47 |
48 | if (subCommand.match(commandRun)) {
49 | const referencedTaskName = subCommand.replace(commandRun, '').trim()
50 | return createTaskReference(referencedTaskName, taskName)
51 | } else if (subCommand.match(commandRunParallel)) {
52 | const spaceSeparatedSubTasks = subCommand.replace(commandRunParallel, '')
53 | const title = subCommand.length > 30 ? subCommand.substr(0, 30) + '...' : subCommand
54 | return createAnonymousConcurrencyTask(spaceSeparatedSubTasks, taskName, title)
55 | } else {
56 | const title = subCommand.replace(/\s.*$/, '')
57 | return createAnonymousCommandTask(title, () => shellObservable(subCommand))
58 | }
59 | }
60 |
61 | function createAnonymousConcurrencyTask (spaceSeparatedSubTasks, taskName, title) {
62 | const referencedTaskNames = spaceSeparatedSubTasks
63 | .split(' ')
64 | .map((subTaskName) => subTaskName.trim())
65 | .filter((subTaskName) => subTaskName.length > 0)
66 |
67 | const taskReferences = referencedTaskNames.map(
68 | (subTaskName) => createTaskReference(subTaskName, taskName)
69 | )
70 | return createAnonymousConcurrentTaskFork(taskName, title, taskReferences)
71 | }
72 |
73 | exports.loadFile = loadFile
74 |
--------------------------------------------------------------------------------
/lib/loaders/test/js.test.js:
--------------------------------------------------------------------------------
1 | const test = require('ava').test
2 | const path = require('path')
3 | const rewire = require('rewire')
4 | const sinon = require('sinon')
5 | const EventEmitter = require('events')
6 |
7 | const jsLoader = rewire('../js')
8 | const taskClasses = require('../../task/classes')
9 |
10 | const FIXTURE_FILE_PATH = path.join(__dirname, '../../../test/fixtures/launch.scripts.js')
11 |
12 | function take (thing, callback) {
13 | callback(thing)
14 | }
15 |
16 | test('loading JS file', (t) => {
17 | const taskCallEmitter = new EventEmitter()
18 | const tasks = jsLoader.loadFile(FIXTURE_FILE_PATH, taskCallEmitter)
19 |
20 | t.deepEqual(
21 | Object.keys(tasks).sort(),
22 | [ 'default', 'fancyTasking', 'npmList', 'runStandardLinter', 'wasteSomeTime', 'willFail' ]
23 | )
24 |
25 | ///////////////////
26 | // Task "default":
27 | take(tasks.default, (task) => {
28 | t.true(task instanceof taskClasses.TaskFork)
29 | t.is(task.name, 'default')
30 | t.is(task.children.length, 3)
31 | })
32 |
33 | take(tasks.default.children[0], (subTask) => {
34 | t.true(subTask instanceof taskClasses.AnonymousCommandTask)
35 | t.is(subTask.title, 'npmList')
36 | })
37 |
38 | ////////////////////////
39 | // Task "fancyTasking":
40 | t.true(tasks.fancyTasking instanceof taskClasses.TaskLeaf)
41 | t.is(tasks.fancyTasking.name, 'fancyTasking')
42 |
43 | ///////////////////
44 | // Task "npmList":
45 | t.true(tasks.npmList instanceof taskClasses.TaskLeaf)
46 | t.is(tasks.npmList.name, 'npmList')
47 |
48 | /////////////////////////////
49 | // Task "runStandardLinter":
50 | t.true(tasks.runStandardLinter instanceof taskClasses.TaskLeaf)
51 | t.is(tasks.runStandardLinter.name, 'runStandardLinter')
52 |
53 | /////////////////////////
54 | // Task "wasteSomeTime":
55 | t.true(tasks.wasteSomeTime instanceof taskClasses.TaskLeaf)
56 | t.is(tasks.wasteSomeTime.name, 'wasteSomeTime')
57 |
58 | ////////////////////
59 | // Task "willFail":
60 | t.true(tasks.willFail instanceof taskClasses.TaskLeaf)
61 | t.is(tasks.willFail.name, 'willFail')
62 | })
63 |
64 | test('observeExports() works, calls cause taskCallEmitter events', (t) => {
65 | const fakeTasks = {
66 | __Rewire__,
67 | fakeTask (...args) {
68 | return 'args: ' + args.join(', ')
69 | },
70 | shouldNotBeTouched: 'some string'
71 | }
72 |
73 | function __Rewire__ (taskName, newMethod) {
74 | fakeTasks[ taskName ] = newMethod
75 | }
76 |
77 | const taskCallEmitter = new EventEmitter()
78 | const calls = []
79 | taskCallEmitter.on('call', (methodName) => calls.push(methodName))
80 |
81 | jsLoader.__get__('observeExports')(fakeTasks, taskCallEmitter)
82 | t.is(calls.length, 0)
83 |
84 | const returnValue = fakeTasks.fakeTask('foo', 'bar')
85 | t.deepEqual(calls, [ 'fakeTask' ])
86 | t.is(returnValue, 'args: foo, bar')
87 |
88 | t.is(fakeTasks.shouldNotBeTouched, 'some string')
89 | })
90 |
91 | test('task instances are created correctly', (t) => {
92 | const method = sinon.spy(() => 'method called')
93 |
94 | const fakeTasks = {
95 | method,
96 | arrayOfMethods: [ method ],
97 | arrayOfStrings: [ 'method' ]
98 | }
99 |
100 | const createdTasks = jsLoader.__get__('createTasksForExports')(fakeTasks)
101 |
102 | ////////////////////
103 | // fakeTask.method:
104 | take(createdTasks.method, (task) => {
105 | t.true(task instanceof taskClasses.TaskLeaf)
106 | t.is(task.name, 'method')
107 | t.is(task.title, 'method')
108 | t.false(method.called)
109 | t.is(task.method(), 'method called')
110 | t.true(method.called)
111 | })
112 |
113 | ////////////////////////////
114 | // fakeTask.arrayOfMethods:
115 | take(createdTasks.arrayOfMethods, (task) => {
116 | t.true(task instanceof taskClasses.TaskFork)
117 | t.is(task.name, 'arrayOfMethods')
118 | t.is(task.title, 'arrayOfMethods')
119 | t.is(task.children.length, 1)
120 | })
121 |
122 | take(createdTasks.arrayOfMethods.children[0], (task) => {
123 | t.true(task instanceof taskClasses.AnonymousCommandTask)
124 | t.is(task.title, 'proxy') // the sinon spy's name is "proxy"
125 | })
126 |
127 | ////////////////////////////
128 | // fakeTask.arrayOfStrings:
129 | take(createdTasks.arrayOfStrings, (task) => {
130 | t.true(task instanceof taskClasses.TaskFork)
131 | t.is(task.name, 'arrayOfStrings')
132 | t.is(task.title, 'arrayOfStrings')
133 | t.is(task.children.length, 1)
134 | })
135 |
136 | take(createdTasks.arrayOfStrings.children[0], (task) => {
137 | t.true(task instanceof taskClasses.TaskReference)
138 | t.is(task.reference, 'method')
139 | t.is(task.referencingTaskName, 'arrayOfStrings')
140 | })
141 | })
142 |
--------------------------------------------------------------------------------
/lib/loaders/test/json.test.js:
--------------------------------------------------------------------------------
1 | const test = require('ava').test
2 | const path = require('path')
3 | const sinon = require('sinon')
4 | const rewire = require('rewire')
5 | const Observable = require('zen-observable')
6 |
7 | const jsonLoader = rewire('../json')
8 | const taskClasses = require('../../task/classes')
9 |
10 | const FIXTURE_FILE_PATH = path.join(__dirname, '../../../test/fixtures/launch.scripts.json5')
11 |
12 | const shellObservable = sinon.spy(() => new Observable((observer) => observer.complete()))
13 | jsonLoader.__set__('shellObservable', shellObservable)
14 |
15 | const tasks = jsonLoader.loadFile(FIXTURE_FILE_PATH)
16 |
17 | function take (thing, callback) {
18 | callback(thing)
19 | }
20 |
21 | function runTask (task) {
22 | task.children.forEach((task) => task.method())
23 | }
24 |
25 | test('all tasks have been loaded', (t) => {
26 | t.deepEqual(
27 | Object.keys(tasks).sort(),
28 | [ 'default', 'defaultAsArray', 'parallel', 'sleep1', 'sleep2', 'willFail', 'willWork' ]
29 | )
30 | })
31 |
32 | test('task "default"', (t) => {
33 | take(tasks.default, (task) => {
34 | t.true(task instanceof taskClasses.TaskFork)
35 | t.is(task.name, 'default')
36 | t.is(task.title, 'default')
37 | t.is(task.children.length, 2)
38 |
39 | take(task.children[0], (subTask) => {
40 | t.true(subTask instanceof taskClasses.TaskReference)
41 | t.is(subTask.reference, 'willWork')
42 | t.is(subTask.referencingTaskName, 'default')
43 | })
44 | take(task.children[1], (subTask) => {
45 | t.true(subTask instanceof taskClasses.TaskReference)
46 | t.is(subTask.reference, 'willFail')
47 | t.is(subTask.referencingTaskName, 'default')
48 | })
49 | })
50 | })
51 |
52 | test('task "defaultAsArray"', (t) => {
53 | take(tasks.defaultAsArray, (task) => {
54 | t.true(task instanceof taskClasses.TaskFork)
55 | t.is(task.name, 'defaultAsArray')
56 | t.is(task.title, 'defaultAsArray')
57 | t.is(task.children.length, 2)
58 |
59 | take(task.children[0], (subTask) => {
60 | t.true(subTask instanceof taskClasses.TaskReference)
61 | t.is(subTask.reference, 'willWork')
62 | t.is(subTask.referencingTaskName, 'defaultAsArray')
63 | })
64 | take(task.children[1], (subTask) => {
65 | t.true(subTask instanceof taskClasses.TaskReference)
66 | t.is(subTask.reference, 'willFail')
67 | t.is(subTask.referencingTaskName, 'defaultAsArray')
68 | })
69 | })
70 | })
71 |
72 | test('task "willWork"', (t) => {
73 | take(tasks.willWork, (task) => {
74 | t.true(task instanceof taskClasses.TaskFork)
75 | t.is(task.name, 'willWork')
76 | t.is(task.title, 'willWork')
77 | t.is(task.children.length, 1)
78 |
79 | take(task.children[0], (subTask) => {
80 | t.true(subTask instanceof taskClasses.AnonymousCommandTask)
81 | t.is(subTask.title, 'npm')
82 | })
83 | })
84 | })
85 |
86 | test('task "willFail"', (t) => {
87 | take(tasks.willFail, (task) => {
88 | t.true(task instanceof taskClasses.TaskFork)
89 | t.is(task.name, 'willFail')
90 | t.is(task.title, 'willFail')
91 | t.is(task.children.length, 1)
92 |
93 | take(task.children[0], (subTask) => {
94 | t.true(subTask instanceof taskClasses.AnonymousCommandTask)
95 | t.is(subTask.title, 'git')
96 | })
97 | })
98 | })
99 |
100 | test('task "parallel"', (t) => {
101 | take(tasks.parallel, (task) => {
102 | t.true(task instanceof taskClasses.TaskFork)
103 | t.is(task.name, 'parallel')
104 | t.is(task.title, 'parallel')
105 | t.is(task.children.length, 1)
106 | })
107 |
108 | take(tasks.parallel.children[0], (task) => {
109 | t.true(task instanceof taskClasses.AnonymousConcurrentTaskFork)
110 | t.is(task.title, 'run-parallel sleep1 sleep2')
111 | t.is(task.children.length, 2)
112 |
113 | take(task.children[0], (taskReference) => {
114 | t.true(taskReference instanceof taskClasses.TaskReference)
115 | t.is(taskReference.reference, 'sleep1')
116 | t.is(taskReference.referencingTaskName, 'parallel')
117 | })
118 |
119 | take(task.children[1], (taskReference) => {
120 | t.true(taskReference instanceof taskClasses.TaskReference)
121 | t.is(taskReference.reference, 'sleep2')
122 | t.is(taskReference.referencingTaskName, 'parallel')
123 | })
124 | })
125 | })
126 |
127 | test('it actually run shell commands', (t) => {
128 | ///////////////////
129 | // Task "willWork"
130 |
131 | t.false(shellObservable.called)
132 | runTask(tasks.willWork)
133 | t.is(shellObservable.callCount, 1)
134 | t.true(shellObservable.calledWith('npm --version'))
135 | shellObservable.reset()
136 |
137 | ///////////////////
138 | // Task "willFail"
139 |
140 | runTask(tasks.willFail)
141 | t.is(shellObservable.callCount, 1)
142 | t.true(shellObservable.calledWith('git push not-existent'))
143 | shellObservable.reset()
144 | })
145 |
--------------------------------------------------------------------------------
/lib/task/classes.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const Listr = require('listr')
4 | const Observable = require('zen-observable')
5 |
6 | const getTaskByName = require('./registry').getTaskByName
7 |
8 | /**
9 | * Abstract base class for tasks.
10 | */
11 | class Task {
12 | constructor (name, title) {
13 | this.name = name
14 | this.title = title || name
15 | }
16 | }
17 |
18 | /**
19 | * Abstract base class for tasks that actually run stuff themselves.
20 | */
21 | class ExecutableTask extends Task {
22 | createListrTask (taskCallsObservable) {
23 | const task = taskCallsObservable
24 | ? () => createObservableFor(this.method, this.name, taskCallsObservable)
25 | : () => this.method()
26 |
27 | return { title: this.title, task }
28 | }
29 | }
30 |
31 | /**
32 | * A TaskLeaf executes a method.
33 | */
34 | class TaskLeaf extends ExecutableTask {
35 | constructor (name, method) {
36 | super(name)
37 | this.method = method
38 | }
39 | }
40 |
41 | /**
42 | * An AnonymousCommandTask is not a real task, but only some shell command.
43 | */
44 | class AnonymousCommandTask extends ExecutableTask {
45 | constructor (title, method) {
46 | super(null, title)
47 | this.method = method
48 | }
49 | }
50 |
51 | /**
52 | * A TaskFork does not directly execute anything, but rather triggers other tasks.
53 | */
54 | class TaskFork extends Task {
55 | constructor (name, children) {
56 | super(name)
57 | this.children = children || []
58 | }
59 |
60 | createListrTask (taskCallsObservable) {
61 | const listrSubTasks = this.children.map((task) => task.createListrTask(taskCallsObservable))
62 |
63 | return {
64 | title: this.title,
65 | task: () => new Listr(listrSubTasks)
66 | }
67 | }
68 | }
69 |
70 | class AnonymousConcurrentTaskFork extends ExecutableTask {
71 | constructor (title, children) {
72 | super(null, title)
73 | this.children = children
74 | }
75 |
76 | createListrTask (taskCallsObservable, listrOptions) {
77 | const listrSubTasks = this.children.map((task) => task.createListrTask(taskCallsObservable))
78 |
79 | return {
80 | title: this.title,
81 | task: () => new Listr(listrSubTasks, { concurrent: true })
82 | }
83 | }
84 | }
85 |
86 | /**
87 | * A TaskReference is just for referencing a task (which might have not been created yet)
88 | */
89 | class TaskReference {
90 | constructor (name, referencingTaskName) {
91 | this.reference = name
92 | this.referencingTaskName = referencingTaskName
93 | }
94 |
95 | resolve () {
96 | const task = getTaskByName(this.reference)
97 |
98 | if (!task) {
99 | throw new Error(`Task not found: "${this.reference}" (referenced by "${this.referencingTaskName}")`)
100 | }
101 | return task
102 | }
103 |
104 | createListrTask () {
105 | return this.resolve().createListrTask()
106 | }
107 | }
108 |
109 | function createObservableFor (method, taskName, taskCallsObservable) {
110 | return new Observable((observer) => {
111 | // inform Listr when another task is run
112 | taskCallsObservable
113 | .filter((calledTaskName) => calledTaskName !== taskName)
114 | .subscribe(observer)
115 |
116 | // run the initial task (`method` a.k.a `taskName`), subscribe to it
117 | observify(() => method()).subscribe(observer)
118 | })
119 | }
120 |
121 | function observify (method) {
122 | return new Observable((observer) => {
123 | const result = method()
124 |
125 | if (result.subscribe) {
126 | // Observable, so pass through
127 | result.subscribe(observer)
128 | } else if (result.then && result.catch) {
129 | // Promise, so map .then/.catch to observer
130 | result.then((value) => {
131 | observer.next(value)
132 | observer.complete()
133 | }).catch((error) => {
134 | observer.error(error)
135 | })
136 | } else {
137 | // Just complete synchronously
138 | observer.next(result)
139 | observer.complete()
140 | }
141 | })
142 | }
143 |
144 | exports.Task = Task
145 | exports.TaskLeaf = TaskLeaf
146 | exports.AnonymousCommandTask = AnonymousCommandTask
147 | exports.TaskFork = TaskFork
148 | exports.AnonymousConcurrentTaskFork = AnonymousConcurrentTaskFork
149 | exports.TaskReference = TaskReference
150 |
--------------------------------------------------------------------------------
/lib/task/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const classes = require('./classes')
4 | const Task = classes.Task
5 | const TaskLeaf = classes.TaskLeaf
6 | const AnonymousCommandTask = classes.AnonymousCommandTask
7 | const TaskFork = classes.TaskFork
8 | const AnonymousConcurrentTaskFork = classes.AnonymousConcurrentTaskFork
9 | const TaskReference = classes.TaskReference
10 |
11 | const registerTask = require('./registry').registerTask
12 |
13 | function createTaskFromMethod (name, method) {
14 | return registerTask(new TaskLeaf(name, method))
15 | }
16 |
17 | function createAnonymousCommandTask (title, method) {
18 | return new AnonymousCommandTask(title, method)
19 | }
20 |
21 | function createTaskFork (name, subTasks) {
22 | subTasks.forEach((subTask) => assertSubTaskOrReference(subTask, name))
23 | return registerTask(new TaskFork(name, subTasks))
24 | }
25 |
26 | function createAnonymousConcurrentTaskFork (parentTaskName, title, subTasks) {
27 | subTasks.forEach((subTask) => assertSubTaskOrReference(subTask, parentTaskName))
28 | return new AnonymousConcurrentTaskFork(title, subTasks)
29 | }
30 |
31 | function createTaskReference (referencedTaskName, referencingTaskName) {
32 | return new TaskReference(referencedTaskName, referencingTaskName)
33 | }
34 |
35 | function createListrTaskItemFor (task, taskCallsObservable) {
36 | if (!(task instanceof Task) && !(task instanceof TaskReference)) {
37 | throw new Error(`Expected Task or TaskReference. Instead got: ${task} (type ${typeof task})`)
38 | }
39 |
40 | return task.createListrTask(taskCallsObservable)
41 | }
42 |
43 | function assertSubTaskOrReference (subTask, superTaskName) {
44 | if (!(subTask instanceof Task) && !(subTask instanceof TaskReference)) {
45 | throw new Error(`Error creating task "${superTaskName}": Found invalid sub-task: ${subTask} (type ${typeof subTask})`)
46 | }
47 | }
48 |
49 | exports.createFromMethod = createTaskFromMethod
50 | exports.createAnonymousCommand = createAnonymousCommandTask
51 | exports.createFork = createTaskFork
52 | exports.createAnonymousConcurrentTaskFork = createAnonymousConcurrentTaskFork
53 | exports.createReference = createTaskReference
54 | exports.createListrTaskItemFor = createListrTaskItemFor
55 |
--------------------------------------------------------------------------------
/lib/task/registry.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const allTasksByName = {}
4 |
5 | function registerTask (task) {
6 | if (allTasksByName[ task.name ]) {
7 | throw new Error(`Attempted double definition of task "${task.name}"`)
8 | }
9 |
10 | allTasksByName[ task.name ] = task
11 | return task
12 | }
13 |
14 | function getTaskByName (name) {
15 | return allTasksByName[ name ]
16 | }
17 |
18 | exports.registerTask = registerTask
19 | exports.getTaskByName = getTaskByName
20 |
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const execa = require('execa')
4 | const Observable = require('zen-observable')
5 |
6 | /**
7 | * @param {string} command
8 | * @return {Promise<{ stdout: string, stderr: string }>}
9 | */
10 | function shell (command) {
11 | return execa.shell(command)
12 | }
13 |
14 | /**
15 | * @param {string} command
16 | * @return {Observable}
17 | * Notifies its subscribers constantly about the lastest line of stdout/stderr output.
18 | */
19 | function shellObservable (command) {
20 | const promisifiedProcess = execa.shell(command)
21 |
22 | return new Observable((observer) => {
23 | promisifiedProcess
24 | .then(() => observer.complete())
25 | .catch((error) => observer.error(error))
26 |
27 | if (promisifiedProcess.stdout) {
28 | observeStream(promisifiedProcess.stdout, (mostRecentLine) => observer.next(mostRecentLine))
29 | }
30 | if (promisifiedProcess.stderr) {
31 | observeStream(promisifiedProcess.stderr, (mostRecentLine) => observer.next(mostRecentLine))
32 | }
33 | })
34 | }
35 |
36 | /**
37 | * @param {Stream} stream
38 | * @param {function} onNewLine function (mostRecentLine: string)
39 | * @return void
40 | */
41 | function observeStream (stream, onNewLine) {
42 | stream.on('data', (text) => {
43 | if (typeof text !== 'string') {
44 | return
45 | }
46 |
47 | const mostRecentLine = text.split('\n').filter((line) => line.length > 0).pop()
48 | onNewLine(mostRecentLine)
49 | })
50 | }
51 |
52 | exports.shell = shell
53 | exports.shellObservable = shellObservable
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "npm-launch",
3 | "version": "0.3.0",
4 | "description": "Minimalistic task runner on steroids.",
5 | "bin": {
6 | "launch": "lib/cli.js"
7 | },
8 | "main": "lib/index.js",
9 | "scripts": {
10 | "coverage": "nyc ./lib/cli.js ava",
11 | "prepublish": "npm test",
12 | "prepush": "npm test",
13 | "test": "./lib/cli.js test"
14 | },
15 | "keywords": [
16 | "launch",
17 | "npm",
18 | "run",
19 | "task",
20 | "package.json",
21 | "json5",
22 | "async",
23 | "await",
24 | "es6",
25 | "script"
26 | ],
27 | "author": "Andy Wermke ",
28 | "license": "MIT",
29 | "engines": {
30 | "node": ">= 4.0"
31 | },
32 | "devDependencies": {
33 | "ava": "^0.15.2",
34 | "husky": "^0.11.4",
35 | "nyc": "^8.1.0",
36 | "rewire": "^2.5.2",
37 | "sinon": "^1.17.4",
38 | "standard": "^7.1.2"
39 | },
40 | "dependencies": {
41 | "babel-plugin-rewire": "^1.0.0-rc-4",
42 | "babel-plugin-transform-async-to-generator": "^6.8.0",
43 | "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3",
44 | "babel-register": "^6.9.0",
45 | "camelcase": "^3.0.0",
46 | "colors": "^1.1.2",
47 | "execa": "^0.4.0",
48 | "json5": "^0.5.0",
49 | "listr": "^0.5.0",
50 | "yargs": "^4.7.1",
51 | "zen-observable": "^0.3.0"
52 | },
53 | "repository": "andywer/npm-launch",
54 | "bugs": "https://github.com/andywer/npm-launch/issues"
55 | }
56 |
--------------------------------------------------------------------------------
/test/fixtures/launch.scripts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Run from command line: `launch []`
3 | */
4 |
5 | const shell = module.parent.exports.shell
6 |
7 | export function npmList () {
8 | // Run a shell command
9 | return shell('npm list')
10 | }
11 |
12 | export function runStandardLinter () {
13 | // Run a binary from node_modules/.bin
14 | return shell('standard --version')
15 | }
16 |
17 | export function wasteSomeTime () {
18 | // You can return any Promise
19 | return new Promise((resolve) => {
20 | setTimeout(() => resolve(), 1500)
21 | })
22 | }
23 |
24 | // Can even use async/await out-of-the-box
25 | export async function fancyTasking () {
26 | await npmList()
27 | await runStandardLinter()
28 | await wasteSomeTime()
29 | }
30 |
31 | // Try `launch willFail` to see what an error looks like
32 | export function willFail () {
33 | return shell('git push not-existent')
34 | }
35 |
36 | // This will set up a task "default" triggering the given tasks one after another
37 | export default [ npmList, runStandardLinter, wasteSomeTime ]
38 |
--------------------------------------------------------------------------------
/test/fixtures/launch.scripts.json5:
--------------------------------------------------------------------------------
1 | /**
2 | * Run from command line: `launch -f launch.scripts.json5 []`
3 | */
4 | {
5 | // Will just run "npm --version"
6 | willWork: "npm --version",
7 |
8 | // Will cause the task to fail
9 | willFail: "git push not-existent",
10 |
11 | // Will run "willWork", then "willFail"
12 | default: "run willWork && run willFail",
13 |
14 | // You can also use array syntax!
15 | defaultAsArray: [ "willWork", "willFail" ],
16 |
17 | parallel: "run-parallel sleep1 sleep2",
18 |
19 | sleep1: "sleep 2.5",
20 | sleep2: "sleep 2 && sleep 2"
21 | }
22 |
--------------------------------------------------------------------------------
/test/functional.test.js:
--------------------------------------------------------------------------------
1 | const test = require('ava').test
2 | const execa = require('execa')
3 | const path = require('path')
4 |
5 | process.chdir('./fixtures')
6 |
7 | const NPM_LAUNCH = '../../lib/cli.js'
8 |
9 | test('running npm-launch --help', () => {
10 | // will reject the returned Promise if something goes wrong
11 | // (test with: execa.shell(`${NPM_LAUNCH} -f`))
12 | return execa.shell(`${NPM_LAUNCH} --help`)
13 | })
14 |
15 | test('running npm-launch --list', async (t) => {
16 | const { stdout } = await execa.shell(`${NPM_LAUNCH} --list`)
17 | const expectedTasks = [ 'default', 'fancyTasking', 'npmList', 'runStandardLinter', 'wasteSomeTime', 'willFail' ]
18 | const expectedOutput = `Tasks in launch.scripts.js:\n${expectedTasks.map((taskName) => ` ${taskName}`).join('\n')}`
19 | t.is(stdout, expectedOutput)
20 | })
21 |
22 | test('running npm-launch', () => {
23 | return execa.shell(`${NPM_LAUNCH}`)
24 | })
25 |
26 | test('running npm-launch default', () => {
27 | return execa.shell(`${NPM_LAUNCH} default`)
28 | })
29 |
30 | test('running npm-launch fancyTasking', () => {
31 | return execa.shell(`${NPM_LAUNCH} fancyTasking`)
32 | })
33 |
34 | test('running npm-launch fancy-tasking', () => {
35 | return execa.shell(`${NPM_LAUNCH} fancy-tasking`)
36 | })
37 |
38 | test('running npm-launch willFail', (t) => {
39 | return t.throws(execa.shell(`${NPM_LAUNCH} willFail`))
40 | })
41 |
42 | test('running npm-launch -f launch.scripts.json5 willWork', () => {
43 | return execa.shell(`${NPM_LAUNCH} -f launch.scripts.json5 willWork`)
44 | })
45 |
46 | test('running npm-launch -f launch.scripts.json5 willFail', (t) => {
47 | return t.throws(execa.shell(`${NPM_LAUNCH} -f launch.scripts.json5 willFail`))
48 | })
49 |
--------------------------------------------------------------------------------