+353
| assert = require('assert') | ||
| resolve = require('path').resolve | ||
| fork = require('child_process').spawn | ||
| EventEmitter = require('events').EventEmitter | ||
| Watcher = require('./watcher') | ||
| ###* | ||
| #* Status codes | ||
| #* R - restart | ||
| #* E - error | ||
| #* D - done (clean exit) | ||
| #* W - work in progress | ||
| #* S - stopped | ||
| ### | ||
| # Which subtask parameters can be changed without restart | ||
| mutable = | ||
| count : yes | ||
| source : no | ||
| cwd : no | ||
| env : no | ||
| persistent : yes | ||
| executable : no | ||
| timeout : yes | ||
| runtime : yes | ||
| watch : yes | ||
| arguments : no | ||
| class Task extends EventEmitter | ||
| @tasks: Object.create(null) | ||
| @create: (name) -> | ||
| return @tasks[name] ?= new Task(name) | ||
| @destroy: (name) -> | ||
| if name of @tasks | ||
| Watcher.stop(@tasks[name].watchHandler) | ||
| delete @tasks[name] | ||
| return | ||
| @status: -> | ||
| now = Date.now() | ||
| status = {} | ||
| for name, task of @tasks | ||
| status[name] = | ||
| count : task.count | ||
| source : task.source | ||
| restart : task.restart | ||
| subtasks : task.subtasks.map (subtask) -> | ||
| pid : subtask.pid | ||
| args : subtask.args | ||
| status : subtask.status | ||
| uptime : now - subtask.start | ||
| return status | ||
| log: -> | ||
| active: yes | ||
| constructor: (@name) -> | ||
| @subtasks = [] | ||
| @watchHandler = (error) => | ||
| if error | ||
| @emit('error', error) | ||
| else | ||
| @restartSubtasks() | ||
| return | ||
| @on('exit', @exitHandler) | ||
| return @ | ||
| # Upgrade task | ||
| upgrade: (options = {}) -> | ||
| restartRequired = no | ||
| for own key of mutable | ||
| try | ||
| assert.deepEqual(@[key], options[key]) | ||
| catch change | ||
| @upgradeParameter(key, options[key]) | ||
| # Force restart when one of non-mutable keys gets modified | ||
| restartRequired = restartRequired or not mutable[key] | ||
| # Restart existing | ||
| if restartRequired and @subtasks.length | ||
| @log("Restart required for #{@name} task group") | ||
| @restartSubtasks() | ||
| # Spawn required | ||
| for index in [0...(@count or 0)] | ||
| subtask = @subtasks[index] | ||
| if not subtask or (subtask.status is 'R' and not subtask.pid) | ||
| @spawn(index) | ||
| # Kill redundant | ||
| while @subtasks.length > (@count or 0) | ||
| @stopSubtask(@subtasks.pop()) | ||
| return | ||
| # Upgrade task parameter with given value | ||
| upgradeParameter: (key, value) -> | ||
| if value? | ||
| @[key] = value | ||
| else | ||
| delete @[key] | ||
| switch key | ||
| when 'watch' | ||
| Watcher.stop(@watchHandler) | ||
| Watcher.start(@cwd, @watch or [], @watchHandler) | ||
| return | ||
| # Spawn subtask | ||
| spawn: (id) -> | ||
| args = @arguments or [] | ||
| binary = process.execPath | ||
| subtask = | ||
| id : id | ||
| status : 'W' | ||
| name : @name | ||
| start : Date.now() | ||
| env : @expandEnv() | ||
| subtask.args = for argument in args | ||
| if Array.isArray(argument) then argument[id] else argument | ||
| eargs = subtask.args.slice() | ||
| if @executable | ||
| binary = @source | ||
| else | ||
| eargs.unshift(@source) | ||
| subtask.process = fork(binary, eargs, { | ||
| stdio : 'pipe' | ||
| cwd : resolve(@cwd) | ||
| env : subtask.env | ||
| }) | ||
| subtask.pid = subtask.process.pid or 0 | ||
| if subtask.pid | ||
| # Setup logger | ||
| subtask.process.stdout.on('data', @logHandler.bind(@, subtask.pid)) | ||
| subtask.process.stderr.on('data', @logHandler.bind(@, subtask.pid)) | ||
| # Setup exit handler | ||
| subtask.process.once('exit', @emit.bind(@, 'exit', subtask)) | ||
| @log("Task #{subtask.pid} (#{@name}) spawned") | ||
| subtask.process.once('error', (error) => | ||
| subtask.status = 'E' | ||
| subtask.code = 255 | ||
| subtask.pid = 0 | ||
| @emit('error', error) | ||
| @log("Failed to start task (#{@name})") | ||
| ) | ||
| @subtasks[id] = subtask | ||
| return | ||
| # Call fn for each subtask | ||
| foreach: (fn, argument) -> | ||
| for subtask in @subtasks | ||
| fn.call(@, subtask, argument) | ||
| return | ||
| # Kill subtask with signal | ||
| killSubtask: (subtask, signal) -> | ||
| if subtask and subtask.pid | ||
| try | ||
| subtask.process.kill(signal) | ||
| catch error | ||
| @log("Failed to kill #{subtask.pid} (#{subtask.name}) with #{signal}") | ||
| return | ||
| # Stop subtask | ||
| stopSubtask: (subtask) -> | ||
| if subtask and subtask.pid | ||
| subtask.process.kill('SIGINT') | ||
| setTimeout((-> | ||
| if subtask.pid | ||
| subtask.process.kill('SIGTERM') | ||
| ), @timeout or 1000) | ||
| # Restart subtask | ||
| restartSubtask: (subtask) -> | ||
| if subtask | ||
| subtask.status = 'R' | ||
| @stopSubtask(subtask) | ||
| return | ||
| # Get subtask by pid | ||
| getPID: (pid) -> | ||
| for subtask in @subtasks when subtask and subtask.pid is pid | ||
| return subtask | ||
| return | ||
| # Kill subtask by pid with signal | ||
| killPID: (pid, signal) -> | ||
| if pid? | ||
| @killSubtask(@getPID(pid), signal) | ||
| else | ||
| @killSubtasks(signal) | ||
| return | ||
| # Restart subtask by pid | ||
| restartPID: (pid) -> | ||
| if pid? | ||
| @restartSubtask(@getPID(pid)) | ||
| else | ||
| @restartSubtasks() | ||
| return | ||
| # Stop subtask by pid | ||
| stopPID: (pid) -> | ||
| if pid? | ||
| @stopSubtask(@getPID(pid)) | ||
| else | ||
| @stopSubtasks() | ||
| return | ||
| # Kill all subtasks | ||
| killSubtasks: -> | ||
| @foreach(@killSubtask) | ||
| return | ||
| # Restart all subtasks | ||
| restartSubtasks: -> | ||
| @foreach(@restartSubtask) | ||
| return | ||
| # Stop all subtasks | ||
| stopSubtasks: -> | ||
| @foreach(@stopSubtask) | ||
| return | ||
| # Drop task | ||
| dropSubtasks: -> | ||
| if @active | ||
| @active = no | ||
| unless @activeSubtasks().length | ||
| Task.destroy(@name) | ||
| else | ||
| @stopSubtasks() | ||
| return | ||
| activeSubtasks: -> | ||
| return @subtasks.filter((subtask) -> subtask.pid) | ||
| exitHandler: (subtask, code, signal) -> | ||
| restartRequired = @persistent | ||
| if code is null | ||
| @log("Task #{subtask.pid} (#{@name}) was killed by #{signal}") | ||
| else | ||
| @log("Task #{subtask.pid} (#{@name}) exited with code #{code}") | ||
| subtask.pid = 0 | ||
| subtask.code = code | ||
| subtask.signal = signal | ||
| delete subtask.process | ||
| if subtask.status isnt 'R' | ||
| if code | ||
| subtask.status = 'E' | ||
| else if signal | ||
| subtask.status = 'S' | ||
| else | ||
| subtask.status = 'D' | ||
| if restartRequired and code | ||
| elapsed = Date.now() - subtask.start | ||
| if elapsed < (@runtime or 1000) | ||
| @log("Restart skipped after #{elapsed}ms (#{@name})") | ||
| restartRequired = no | ||
| # Restart requested | ||
| if subtask.status is 'R' | ||
| restartRequired = yes | ||
| # Task dropped | ||
| unless @active | ||
| restartRequired = no | ||
| unless @activeSubtasks().length | ||
| Task.destroy(@name) | ||
| if restartRequired | ||
| @spawn(subtask.id) | ||
| return | ||
| logHandler: (pid, data) -> | ||
| @log("#{pid} (#{@name}) #{data}") | ||
| return | ||
| expandEnv: -> | ||
| expanded = {} | ||
| expanded.HOME = process.env.HOME | ||
| expanded.PATH = process.env.PATH | ||
| unless @executable | ||
| expanded.NODE_PATH = process.env.NODE_PATH | ||
| for own key, value of @env | ||
| switch value | ||
| when true | ||
| if process.env.hasOwnProperty(key) | ||
| expanded[key] = process.env[key] | ||
| when false | ||
| delete expanded[key] | ||
| else | ||
| expanded[key] = @env[key] | ||
| return expanded | ||
| module.exports = Task |
| glob = require('glob') | ||
| fs = require('fs') | ||
| util = require('util') | ||
| watches = Object.create(null) | ||
| watcher = Object.create(null) | ||
| class Watcher | ||
| log: -> | ||
| start: (cwd, patterns, callback) -> | ||
| fn = @watch.bind(@, cwd, callback) | ||
| options = | ||
| cwd : cwd | ||
| nomount : yes | ||
| for pattern in patterns | ||
| glob(pattern, options, fn) | ||
| return | ||
| stop: (callback) -> | ||
| for file of watches | ||
| watches[file] = watches[file] | ||
| .filter (item) -> item isnt callback | ||
| unless watches[file] | ||
| # Stop watching file completely | ||
| watcher[file].close() | ||
| delete watches[file] | ||
| delete watcher[file] | ||
| return | ||
| watch: (cwd, callback, error, files) -> | ||
| if error | ||
| callback(error) | ||
| return | ||
| for file in files | ||
| if file[0] isnt '/' | ||
| # Expand to full path | ||
| file = cwd + '/' + file | ||
| callbacks = watches[file] | ||
| if callbacks | ||
| # Add callback to watches | ||
| callbacks.push(callback) | ||
| else | ||
| # Start watching file | ||
| watches[file] = [callback] | ||
| watcher[file] = fs.watch(file, persistent: no, @watchHandler.bind(@, file)) | ||
| return | ||
| watchHandler: (file, event) -> | ||
| if file of watches | ||
| @log("File #{file} changed") | ||
| for callback in watches[file] | ||
| callback(null) | ||
| return | ||
| module.exports = new Watcher() |
| assert = require('assert') | ||
| resolve = require('path').resolve | ||
| zs = require('z-schema') | ||
| schema = require('./schema') | ||
| EventEmitter = require('events').EventEmitter | ||
| Task = require('./task') | ||
| Watcher = require('./watcher') | ||
| validator = new zs(strictMode: true) | ||
| class Weaver extends EventEmitter | ||
| @logger: (fn) -> | ||
| for item in [Weaver, Task, Watcher.constructor] | ||
| item::log = fn | ||
| return | ||
| version : require('../package').version | ||
| log : -> | ||
| constructor: -> | ||
| @on('error', @errorHandler) | ||
| @on('upgrade', @upgradeHandler) | ||
| @start = Date.now() | ||
| @config = Object.create(null) | ||
| return @ | ||
| # Validate configuration object | ||
| validate: (configuration) -> | ||
| # Validate schema | ||
| assert.ok(validator.validate(configuration, schema), 'Invalid configuration') | ||
| # Perform additional validation | ||
| for own name, task of configuration.tasks | ||
| # Validate nested arrays for arguments | ||
| for own key of task | ||
| if key is 'arguments' | ||
| for argument in task[key] | ||
| if Array.isArray(argument) | ||
| assert.equal( | ||
| task.count, argument.length, | ||
| "Nested array in arguments should contain #{task.count} values" | ||
| ) | ||
| task.name = name | ||
| return configuration | ||
| # Upgrade current state | ||
| upgrade: (data, path) -> | ||
| parts = [path] | ||
| params = null | ||
| try | ||
| # Try to parse JSON | ||
| data = JSON.parse(data) | ||
| # Validate new state | ||
| params = @validate(data) | ||
| catch error | ||
| error.message = "Config error: #{error.message}" | ||
| @emit('error', error) | ||
| if params | ||
| if params.path | ||
| parts.push(params.path) | ||
| for own name, task of params.tasks | ||
| task.cwd = resolve.apply(undefined, parts.concat(task.cwd or '.')) | ||
| @config[name] = task | ||
| @emit('upgrade') | ||
| return | ||
| # Get status report | ||
| status: -> | ||
| return Task.status() | ||
| # Execute command with given arguments | ||
| command: (action, name, args) -> | ||
| fn = Task::[action + 'PID'] | ||
| unless Array.isArray(args) | ||
| args = [] | ||
| unless typeof fn is 'function' | ||
| throw new Error('Unknown action ' + action) | ||
| unless name? | ||
| # Execute command for all tasks | ||
| for own name, task of Task.tasks | ||
| fn.apply(task, args) | ||
| else | ||
| task = Task.tasks[name] | ||
| if task | ||
| if action is 'kill' | ||
| args.unshift(null) | ||
| fn.apply(task, args) | ||
| else if `Number(name) == name` | ||
| args.unshift(Number(name)) | ||
| @command(action, null, args) | ||
| else | ||
| @log('Task ' + name + ' was not found') | ||
| return | ||
| # Stop all subtasks and exit | ||
| die: (code) -> | ||
| code = if code? then code else 1 | ||
| tryExit = => | ||
| for own name, task of Task.tasks | ||
| for subtask in task.subtasks | ||
| return if subtask.pid | ||
| @emit('exit', code) | ||
| return | ||
| for own name, task of Task.tasks | ||
| if task.timeout > timeout | ||
| timeout = task.timeout | ||
| task.dropSubtasks() | ||
| task.on('exit', tryExit) | ||
| setImmediate(tryExit) | ||
| return | ||
| errorHandler: (error) -> | ||
| @log(error.message) | ||
| return | ||
| upgradeHandler: -> | ||
| # Spot dropped tasks | ||
| for own name, task of Task.tasks | ||
| unless name of @config | ||
| task.dropSubtasks() | ||
| # Create or update tasks | ||
| for own name, options of @config | ||
| task = Task.create(name) | ||
| # Setup error handler | ||
| unless task.listeners('error').length | ||
| task.on('error', @emit.bind(@, 'error')) | ||
| task.upgrade(options) | ||
| return | ||
| module.exports = new Weaver() |
| assert = require('assert') | ||
| exec = require('child_process').exec | ||
| spawn = require('child_process').spawn | ||
| write = require('fs').writeFileSync | ||
| unlink = require('fs').unlinkSync | ||
| daemon = '../bin/weaver' | ||
| port = 58015 | ||
| config = "#{__dirname}/weaver_#{port}.json" | ||
| random1 = String(Math.random()) | ||
| random2 = String(Math.random()) | ||
| options = | ||
| cwd: __dirname | ||
| env: | ||
| PATH: process.env.PATH | ||
| RND1: random1 | ||
| RND2: random2 | ||
| WEAVER_TEST: 1 | ||
| WEAVER_PORT: port | ||
| log = '' | ||
| monitor = spawn daemon, ['monitor'], options | ||
| monitor.stdout.on 'data', (data) -> log += String(data) | ||
| (require 'vows') | ||
| .describe('env') | ||
| .addBatch | ||
| start: | ||
| topic: -> | ||
| # Write config | ||
| write config, JSON.stringify | ||
| tasks: | ||
| dump: | ||
| count: 1 | ||
| executable: yes | ||
| source: 'bin/env' | ||
| env: | ||
| RND2 : yes | ||
| RND3 : yes | ||
| HOME : no | ||
| PORT : String(port) | ||
| # Start daemon | ||
| exec "#{daemon} --config #{config}", options, @callback | ||
| return | ||
| code: (error, stdout, stderr) -> assert not error | ||
| stdout: (error, stdout, stderr) -> assert not stdout | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| status: | ||
| topic: -> | ||
| # Check status | ||
| exec "#{daemon} status", options, (args...) => | ||
| args[1] = args[1] | ||
| .replace(/\n$/, '') | ||
| .split(/\n/) | ||
| .map(($_) -> +/^\s*(\d+)/.exec($_)[1]) | ||
| setTimeout((=> | ||
| @callback(args...) | ||
| ), 250) | ||
| return | ||
| code: (error, stdout, stderr) -> assert not error | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| stdout: (error, stdout, stderr) -> assert.equal stdout.length, 2 | ||
| env: (error, stdout, stderr) -> | ||
| if /^(\d+) \(dump\) ({.+})/m.exec(log) | ||
| pid = RegExp.$1 | ||
| env = JSON.parse(RegExp.$2) | ||
| else | ||
| assert 0, 'UDP log failed' | ||
| assert.equal env.PORT, port | ||
| assert.equal env.PATH, process.env.PATH | ||
| assert.equal env.$PID, pid | ||
| assert.equal env.RND2, random2 | ||
| assert not env.hasOwnProperty 'HOME' | ||
| assert not env.hasOwnProperty 'RND1' | ||
| assert not env.hasOwnProperty 'RND3' | ||
| exit: | ||
| topic: -> | ||
| # Remove config file | ||
| unlink config | ||
| # Stop monitor | ||
| monitor.kill('SIGTERM') | ||
| # Stop daemon | ||
| exec "#{daemon} exit", options, @callback | ||
| return | ||
| code: (error, stdout, stderr) -> assert not error | ||
| stdout: (error, stdout, stderr) -> assert not stdout | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| .export(module) |
Sorry, the diff of this file is not supported yet
+14
| # 0.3.0 | ||
| # 0.3.1 | ||
| * Command for runtime exception | ||
| * Test for daemon killed with signal | ||
| * Test for runtime exception | ||
| * Test for watchHandler on dropped task | ||
| * Precompile coffee | ||
| # 0.3.2 | ||
| * Verbose logging | ||
| * Pass config over TCP (always) |
+10
-0
| # Changelog | ||
| ## 0.3.0 | ||
| Released 2016-09-05 | ||
| * Starting script now monitors for available TCP connection | ||
| * Configuration is passed only over established TCP connection | ||
| * Daemon finishes when tasks exit, not after longest timeout | ||
| * Refactored core classes (CoffeeScript now) | ||
| * Improved uncaughtException and error handling | ||
| ## 0.2.3 | ||
@@ -4,0 +14,0 @@ |
+1
-1
@@ -9,2 +9,2 @@ test: compile | ||
| clean: | ||
| rm t/*.js | ||
| rm -f t/*.js |
+7
-7
| { | ||
| "name" : "weaver", | ||
| "version" : "0.2.3", | ||
| "version" : "0.3.0", | ||
| "license" : "LGPL-3.0", | ||
@@ -19,11 +19,11 @@ "author" : "Alexander Nazarov <n4kz@n4kz.com>", | ||
| "dependencies": { | ||
| "glob" : "*", | ||
| "optimist" : "*", | ||
| "sprintf" : "*", | ||
| "z-schema" : "*" | ||
| "coffee-script" : ">=1.7.1", | ||
| "glob" : "*", | ||
| "optimist" : "*", | ||
| "sprintf" : "*", | ||
| "z-schema" : "*" | ||
| }, | ||
| "devDependencies": { | ||
| "coffee-script" : ">=1.6.0", | ||
| "vows" : "*" | ||
| "vows": "*" | ||
| }, | ||
@@ -30,0 +30,0 @@ |
+35
-30
@@ -1,7 +0,9 @@ | ||
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| emitter = require('events').EventEmitter | ||
| assert = require('assert') | ||
| Weaver = require('../lib/weaver.coffee') | ||
| Task = require('../lib/task.coffee') | ||
| Watcher = require('../lib/watcher.coffee') | ||
| EventEmitter = require('events').EventEmitter | ||
| methods = [ | ||
| 'define', 'task', 'log', 'validate', 'die', 'upgrade', | ||
| 'log', 'validate', 'die', 'upgrade', | ||
| 'status', 'command' | ||
@@ -16,44 +18,47 @@ ] | ||
| constructor: -> | ||
| assert.instanceOf weaver, weaver.constructor | ||
| assert.instanceOf weaver, emitter | ||
| assert.instanceOf Weaver, Weaver.constructor | ||
| assert.instanceOf Weaver, EventEmitter | ||
| properties: -> | ||
| # version | ||
| assert.equal weaver.version, require('../package').version | ||
| assert.equal Weaver.version, require('../package').version | ||
| # start | ||
| assert.isNumber weaver.start | ||
| assert weaver.start <= Date.now() | ||
| assert weaver.start > 0 | ||
| assert.isNumber Weaver.start | ||
| assert Weaver.start <= Date.now() | ||
| assert Weaver.start > 0 | ||
| # tasks | ||
| assert.deepEqual weaver.tasks, {} | ||
| assert.typeOf weaver.tasks, 'object' | ||
| # config | ||
| assert.deepEqual Weaver.config, {} | ||
| assert.typeOf Weaver.config, 'object' | ||
| assert.isUndefined weaver.tasks.__proto__ | ||
| assert not Weaver.config.__proto__? | ||
| # config | ||
| assert.deepEqual weaver.config, {} | ||
| assert.typeOf weaver.config, 'object' | ||
| logger: -> | ||
| assert.isFunction Weaver.constructor.logger | ||
| assert.isUndefined weaver.config.__proto__ | ||
| methods: -> | ||
| for method in methods | ||
| assert.isFunction weaver[method] | ||
| assert not weaver.propertyIsEnumerable method | ||
| assert.isFunction Weaver[method] | ||
| define: -> | ||
| noop = -> | ||
| events: -> | ||
| for event in events | ||
| assert.equal EventEmitter.listenerCount(Weaver, event), 1 | ||
| weaver.define 'method', 'noop', noop | ||
| watcher: -> | ||
| assert.instanceOf Watcher, Watcher.constructor | ||
| # New method defined | ||
| assert.equal weaver.noop, noop | ||
| assert not weaver.propertyIsEnumerable 'noop' | ||
| assert.isFunction Watcher.stop | ||
| assert.isFunction Watcher.start | ||
| events: -> | ||
| for event in events | ||
| assert.equal emitter.listenerCount(weaver, event), 1 | ||
| task: -> | ||
| assert.isFunction Task | ||
| assert.isFunction Task.create | ||
| assert.isFunction Task.status | ||
| assert.deepEqual Task.tasks, {} | ||
| assert.typeOf Task.tasks, 'object' | ||
| assert not Task.tasks.__proto__? | ||
| .export(module) |
+37
-28
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| emitter = require('events').EventEmitter | ||
| Task = require('../lib/task.coffee') | ||
| methods = [ | ||
| 'upgrade', 'upgradeParameter', 'get', 'spawn', 'foreach', | ||
| 'upgrade', 'upgradeParameter', 'spawn', 'foreach', | ||
| 'killSubtask', 'stopSubtask', 'restartSubtask', | ||
| 'killPID', 'stopPID', 'restartPID', | ||
| 'getPID', 'killPID', 'stopPID', 'restartPID', | ||
| 'stopSubtasks', 'killSubtasks', 'restartSubtasks', | ||
| 'log', 'exitHandler' | ||
| 'exitHandler', 'expandEnv' | ||
| ] | ||
| defaultName = 'test' + Date.now() | ||
| weaver.task defaultName, {} | ||
| Task | ||
| .create defaultName | ||
| .upgrade {} | ||
@@ -22,26 +23,26 @@ (require 'vows') | ||
| name = defaultName | ||
| task = weaver.tasks[name] | ||
| task = Task.tasks[name] | ||
| assert.equal name, task.name | ||
| assert.isArray task.subtasks | ||
| assert.isArray task.watch | ||
| assert.isArray task.arguments | ||
| assert.isObject task.env | ||
| assert.isArray task.subtasks | ||
| assert.isFunction task.watchHandler | ||
| assert.equal 1000, task.timeout | ||
| assert.equal 1000, task.runtime | ||
| assert.equal 0, task.count | ||
| assert.equal '', task.source | ||
| assert.equal false, task.executable | ||
| assert.equal false, task.persistent | ||
| assert.equal process.cwd(), task.cwd | ||
| assert.isUndefined task.timeout | ||
| assert.isUndefined task.runtime | ||
| assert.isUndefined task.count | ||
| assert.isUndefined task.source | ||
| assert.isUndefined task.executable | ||
| assert.isUndefined task.persistent | ||
| assert.isUndefined task.cwd | ||
| assert.isUndefined task.env | ||
| assert.isUndefined task.arguments | ||
| assert.isUndefined task.watch | ||
| methods: -> | ||
| name = defaultName | ||
| task = weaver.tasks[name] | ||
| task = Task.tasks[name] | ||
| for method in methods | ||
| assert.isFunction task[method] | ||
| assert not task.propertyIsEnumerable method | ||
@@ -51,15 +52,23 @@ constructor: -> | ||
| task = weaver.task name, {} | ||
| task = Task.create name | ||
| assert.equal task.constructor, weaver.task | ||
| assert.equal task, weaver.tasks[name] | ||
| assert.equal 1000, task.runtime | ||
| assert.equal 1000, task.timeout | ||
| assert.equal task, Task.tasks[name] | ||
| assert.equal task, weaver.task name, runtime: 2000 | ||
| assert.isUndefined task.runtime | ||
| assert.isUndefined task.timeout | ||
| Task | ||
| .create name | ||
| .upgrade runtime: 2000 | ||
| assert.equal 2000, task.runtime | ||
| assert.equal task, weaver.task name, timeout: 5000 | ||
| assert.equal task, Task.tasks[name] | ||
| Task | ||
| .create name | ||
| .upgrade timeout: 5000 | ||
| assert.equal 5000, task.timeout | ||
| assert.equal task, weaver.tasks[name] | ||
| assert.equal task, Task.tasks[name] | ||
| .export(module) |
+68
-69
@@ -1,4 +0,3 @@ | ||
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| emitter = require('events').EventEmitter | ||
| assert = require('assert') | ||
| Weaver = require('../lib/weaver.coffee') | ||
@@ -10,16 +9,16 @@ (require 'vows') | ||
| basic: -> | ||
| assert.throws -> weaver.validate undefined | ||
| assert.throws -> weaver.validate null | ||
| assert.throws -> weaver.validate [] | ||
| assert.throws -> weaver.validate {} | ||
| assert.throws -> weaver.validate tasks: null | ||
| assert.throws -> weaver.validate tasks: [] | ||
| assert.throws -> weaver.validate tasks: {} | ||
| assert.throws -> weaver.validate tasks: test: null | ||
| assert.throws -> weaver.validate tasks: test: [] | ||
| assert.throws -> weaver.validate tasks: test: {} | ||
| assert.throws -> Weaver.validate undefined | ||
| assert.throws -> Weaver.validate null | ||
| assert.throws -> Weaver.validate [] | ||
| assert.throws -> Weaver.validate {} | ||
| assert.throws -> Weaver.validate tasks: null | ||
| assert.throws -> Weaver.validate tasks: [] | ||
| assert.throws -> Weaver.validate tasks: {} | ||
| assert.throws -> Weaver.validate tasks: test: null | ||
| assert.throws -> Weaver.validate tasks: test: [] | ||
| assert.throws -> Weaver.validate tasks: test: {} | ||
| # Okay | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -33,3 +32,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| path: null | ||
@@ -43,3 +42,3 @@ tasks: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| path: '' | ||
@@ -53,3 +52,3 @@ tasks: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| path: '.' | ||
@@ -64,3 +63,3 @@ tasks: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -72,3 +71,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -80,3 +79,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -88,3 +87,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -97,3 +96,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -106,3 +105,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -116,3 +115,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -124,3 +123,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -132,3 +131,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -140,3 +139,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -149,3 +148,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -159,3 +158,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -168,3 +167,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -178,3 +177,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -188,3 +187,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -199,3 +198,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -208,3 +207,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -218,3 +217,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -228,3 +227,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -239,3 +238,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -248,3 +247,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -258,3 +257,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -268,3 +267,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -279,3 +278,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -289,3 +288,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -300,3 +299,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -310,3 +309,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -321,3 +320,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -330,3 +329,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -340,3 +339,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -350,3 +349,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -360,3 +359,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -370,3 +369,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -382,3 +381,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -391,3 +390,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -401,3 +400,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -411,3 +410,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -421,3 +420,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -431,3 +430,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -441,3 +440,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -451,3 +450,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -461,3 +460,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -471,3 +470,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -482,3 +481,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -491,3 +490,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -501,3 +500,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -512,3 +511,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -522,3 +521,3 @@ test: | ||
| assert.doesNotThrow -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -536,3 +535,3 @@ test: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -545,3 +544,3 @@ '': | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -555,3 +554,3 @@ '❄': | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| whoa: 'so unexpected' | ||
@@ -565,3 +564,3 @@ tasks: | ||
| assert.throws -> | ||
| weaver.validate | ||
| Weaver.validate | ||
| tasks: | ||
@@ -568,0 +567,0 @@ test: |
+19
-19
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| Weaver = require('../lib/weaver.coffee') | ||
| exec = require('child_process').exec | ||
@@ -24,3 +24,3 @@ daemon = '../bin/weaver' | ||
| stdout: (error, stdout, stderr) -> | ||
| assert.include stdout, weaver.version | ||
| assert.include stdout, Weaver.version | ||
@@ -61,3 +61,3 @@ help: | ||
| topic: -> | ||
| exec "#{daemon} status --nocolor", options, @callback | ||
| exec "#{daemon} status", options, @callback | ||
| return | ||
@@ -67,3 +67,3 @@ | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| version: (error, stdout, stderr) -> assert.include stdout, weaver.version | ||
| version: (error, stdout, stderr) -> assert.include stdout, Weaver.version | ||
| name: (error, stdout, stderr) -> assert.include stdout, 'weaver' | ||
@@ -73,22 +73,22 @@ pid: (error, stdout, stderr) -> assert.match stdout, /^\s*\d+\s/ | ||
| exit: | ||
| topic: -> | ||
| exec "#{daemon} exit", options, @callback | ||
| return | ||
| code: (error, stdout, stderr) -> assert not error | ||
| stdout: (error, stdout, stderr) -> assert not stdout | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| status: | ||
| exit: | ||
| topic: -> | ||
| exec "#{daemon} status", options, @callback | ||
| exec "#{daemon} exit", options, @callback | ||
| return | ||
| code: (error, stdout, stderr) -> assert error?.code | ||
| code: (error, stdout, stderr) -> assert not error | ||
| stdout: (error, stdout, stderr) -> assert not stdout | ||
| stderr: (error, stdout, stderr) -> | ||
| assert.include stderr, 'Could not connect' | ||
| assert.include stderr, port | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| status: | ||
| topic: -> | ||
| exec "#{daemon} status", options, @callback | ||
| return | ||
| code: (error, stdout, stderr) -> assert error?.code | ||
| stdout: (error, stdout, stderr) -> assert not stdout | ||
| stderr: (error, stdout, stderr) -> | ||
| assert.include stderr, 'Could not connect' | ||
| assert.include stderr, port | ||
| .export(module) |
+4
-12
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -24,6 +23,3 @@ spawn = require('child_process').spawn | ||
| topic: -> | ||
| exec daemon, options, (args...) => | ||
| setTimeout((=> | ||
| @callback(args...) | ||
| ), 50) | ||
| exec daemon, options, @callback | ||
@@ -40,10 +36,6 @@ return | ||
| exec "#{daemon} exit", options, (args...) => | ||
| # Wait for message to arrive | ||
| setTimeout((=> | ||
| # Remove zombie | ||
| monitor.kill('SIGTERM') | ||
| monitor.kill('SIGTERM') | ||
| # Run tests | ||
| @callback(args...) | ||
| ), 50) | ||
| @callback(args...) | ||
| return | ||
@@ -50,0 +42,0 @@ |
+19
-10
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -16,2 +15,5 @@ write = require('fs').writeFileSync | ||
| bpid1 = 0 | ||
| bpid2 = 0 | ||
| (require 'vows') | ||
@@ -22,7 +24,4 @@ .describe('upgrade') | ||
| topic: -> | ||
| # Write empty config | ||
| write config, JSON.stringify tasks: {} | ||
| # Start daemon | ||
| exec "#{daemon} --config #{config}", options, @callback | ||
| exec daemon, options, @callback | ||
| return | ||
@@ -52,3 +51,5 @@ | ||
| # Check status | ||
| exec "#{daemon} status --nocolor", options, @callback | ||
| exec "#{daemon} status", options, (args...) => | ||
| setTimeout((=> @callback(args...)), 250) | ||
| return | ||
@@ -89,3 +90,5 @@ | ||
| # Check status | ||
| exec "#{daemon} status --nocolor", options, @callback | ||
| exec "#{daemon} status", options, (args...) => | ||
| setTimeout((=> @callback(args...)), 250) | ||
| return | ||
@@ -102,6 +105,6 @@ | ||
| assert.match status[1], /^ *(\d+) +W +\d+s +base +sleep 2006/ | ||
| pid1 = +RegExp.$1 | ||
| bpid1 = pid1 = +RegExp.$1 | ||
| assert.match status[2], /^ *(\d+) +W +\d+s +base +sleep 2006/ | ||
| pid2 = +RegExp.$1 | ||
| bpid2 = pid2 = +RegExp.$1 | ||
@@ -134,3 +137,5 @@ assert.match status[3], /^ *(\d+) +D +\d+s +bonus +uname -a/ | ||
| # Check status | ||
| exec "#{daemon} status --nocolor", options, @callback | ||
| exec "#{daemon} status", options, (args...) => | ||
| setTimeout((=> @callback(args...)), 250) | ||
| return | ||
@@ -158,2 +163,6 @@ | ||
| # Base subtasks should retain pids | ||
| assert.equal bpid1, pid1 | ||
| assert.equal bpid2, pid2 | ||
| assert.notEqual pid1, pid2 | ||
@@ -160,0 +169,0 @@ assert.notEqual pid1, pid3 |
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -4,0 +3,0 @@ write = require('fs').writeFileSync |
+0
-1
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -4,0 +3,0 @@ write = require('fs').writeFileSync |
+0
-1
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -4,0 +3,0 @@ write = require('fs').writeFileSync |
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -46,3 +45,3 @@ write = require('fs').writeFileSync | ||
| # Check status | ||
| exec "#{daemon} status --nocolor", options, (args...) => | ||
| exec "#{daemon} status", options, (args...) => | ||
| args[1] = args[1] | ||
@@ -64,3 +63,3 @@ .replace(/\n$/, '') | ||
| write config, configData | ||
| exec "#{daemon} status --nocolor", options, (args...) => @callback(args..., pid) | ||
| exec "#{daemon} status", options, (args...) => @callback(args..., pid) | ||
| return | ||
@@ -86,3 +85,3 @@ | ||
| # Check status | ||
| exec "#{daemon} status --nocolor", options, (args...) => | ||
| exec "#{daemon} status", options, (args...) => | ||
| args[1] = args[1] | ||
@@ -104,3 +103,3 @@ .replace(/\n$/, '') | ||
| write config, configData | ||
| exec "#{daemon} status --nocolor", options, (args...) => @callback(args..., pid) | ||
| exec "#{daemon} status", options, (args...) => @callback(args..., pid) | ||
| return | ||
@@ -107,0 +106,0 @@ |
+0
-1
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -4,0 +3,0 @@ write = require('fs').writeFileSync |
+0
-1
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -4,0 +3,0 @@ write = require('fs').writeFileSync |
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -4,0 +3,0 @@ write = require('fs').writeFileSync |
+46
-15
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| exec = require('child_process').exec | ||
@@ -36,2 +35,7 @@ write = require('fs').writeFileSync | ||
| arguments: [2014] | ||
| s3: | ||
| count: 1 | ||
| executable: yes | ||
| source: 'uname' | ||
| arguments: ['-a'] | ||
@@ -49,3 +53,3 @@ # Start daemon | ||
| # Check status | ||
| exec "#{daemon} status --nocolor", options, (args...) => | ||
| exec "#{daemon} status", options, (args...) => | ||
| args[1] = args[1] | ||
@@ -61,3 +65,3 @@ .replace(/\n$/, '') | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| stdout: (error, stdout, stderr) -> assert.equal stdout.length, 7 | ||
| stdout: (error, stdout, stderr) -> assert.equal stdout.length, 8 | ||
@@ -69,7 +73,7 @@ drop: | ||
| # Get configuration dump | ||
| exec "#{daemon} dump --nocolor", options, (args...) => | ||
| exec "#{daemon} dump", options, (args...) => | ||
| dump = JSON.parse args[1] | ||
| # Check status | ||
| exec "#{daemon} status --nocolor", options, (args...) => | ||
| exec "#{daemon} status", options, (args...) => | ||
| @callback(args..., pid, dump) | ||
@@ -87,3 +91,3 @@ return | ||
| assert.equal status.length, 5 | ||
| assert.equal status.length, 6 | ||
@@ -96,16 +100,43 @@ assert.match stdout, /^(?:[\s\S](?!s2))+$/ | ||
| assert.equal pid[4], status[4] | ||
| assert.equal 0, status[5] | ||
| exit: | ||
| topic: -> | ||
| # Remove config file | ||
| unlink config | ||
| drop: | ||
| topic: (stdout, stderr, pid) -> | ||
| # Drop finished task | ||
| exec "#{daemon} drop s3", options, => | ||
| # Check status | ||
| exec "#{daemon} status", options, (args...) => | ||
| @callback(args..., pid) | ||
| # Stop daemon | ||
| exec "#{daemon} exit", options, @callback | ||
| return | ||
| code: (error, stdout, stderr) -> assert not error | ||
| stdout: (error, stdout, stderr) -> assert not stdout | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| code: (error, stdout, stderr, pid) -> assert not error | ||
| stderr: (error, stdout, stderr, pid) -> assert not stderr | ||
| stdout: (error, stdout, stderr, pid) -> | ||
| status = stdout | ||
| .replace(/\n$/, '') | ||
| .split(/\n/) | ||
| .map(($_) -> +/^\s*(\d+)/.exec($_)[1]) | ||
| assert.equal status.length, 5 | ||
| assert.match stdout, /^(?:[\s\S](?!s[23]))+$/ | ||
| assert.equal pid[1], status[1] | ||
| assert.equal pid[2], status[2] | ||
| assert.equal pid[3], status[3] | ||
| assert.equal pid[4], status[4] | ||
| exit: | ||
| topic: -> | ||
| # Remove config file | ||
| unlink config | ||
| # Stop daemon | ||
| exec "#{daemon} exit", options, @callback | ||
| return | ||
| code: (error, stdout, stderr) -> assert not error | ||
| stdout: (error, stdout, stderr) -> assert not stdout | ||
| stderr: (error, stdout, stderr) -> assert not stderr | ||
| .export(module) |
| 'use strict'; | ||
| var glob = require('glob'), | ||
| fs = require('fs'), | ||
| util = require('util'), | ||
| events = require('events'), | ||
| watches = {}, | ||
| watcher = {}; | ||
| function handler (weaver, file, event) { | ||
| weaver.log('File ' + file + ' changed'); | ||
| (watches[file] || []).forEach(function (callback) { | ||
| callback(); | ||
| }); | ||
| } | ||
| function watch (weaver, cwd, callback, error, files) { | ||
| var cbs, file, i, l; | ||
| if (error) { | ||
| weaver.emit('error', error); | ||
| return; | ||
| } | ||
| for (i = 0, l = files.length; i < l; i++) { | ||
| file = files[i]; | ||
| if (file[0] !== '/') { | ||
| /* Expand to full path */ | ||
| file = cwd + '/' + file; | ||
| } | ||
| cbs = watches[file]; | ||
| if (cbs) { | ||
| /* Add callback to watches */ | ||
| cbs.push(callback); | ||
| } else { | ||
| /* Start watching file */ | ||
| watches[file] = [callback]; | ||
| watcher[file] = fs.watch(file, handler.bind(undefined, weaver, file)); | ||
| } | ||
| } | ||
| } | ||
| function start (weaver, cwd, patterns, callback) { | ||
| var fn = watch.bind(undefined, weaver, cwd, callback), | ||
| i, l; | ||
| for (i = 0, l = patterns.length; i < l; i++) { | ||
| glob(patterns[i], { | ||
| cwd : cwd, | ||
| nomount : true | ||
| }, fn); | ||
| } | ||
| } | ||
| function stop (callback) { | ||
| var cbs, i, file; | ||
| for (file in watches) { | ||
| if (watches.hasOwnProperty(file)) { | ||
| cbs = watches[file]; | ||
| i = cbs.length; | ||
| while (i--) { | ||
| if (cbs[i] === callback) { | ||
| cbs.splice(i, 1); | ||
| } | ||
| } | ||
| if (!cbs.length) { | ||
| /* Stop watching file completely */ | ||
| watcher[file].close(); | ||
| delete watches[file]; | ||
| delete watcher[file]; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function Watcher () { | ||
| this.start = start; | ||
| this.stop = stop; | ||
| return this; | ||
| } | ||
| util.inherits(Watcher, events.EventEmitter); | ||
| module.exports = new Watcher(); |
-880
| 'use strict'; | ||
| var fs = require('fs'), | ||
| util = require('util'), | ||
| events = require('events'), | ||
| assert = require('assert'), | ||
| resolve = require('path').resolve, | ||
| fork = require('child_process').spawn, | ||
| zs = require('z-schema'), | ||
| schema = require('./schema'), | ||
| Watcher = require('./watcher'), | ||
| sprintf = require('sprintf').sprintf, | ||
| /* Which subtask parameters can be changed without restart */ | ||
| mutable = { | ||
| count : true, | ||
| source : false, | ||
| cwd : false, | ||
| env : false, | ||
| persistent : true, | ||
| executable : false, | ||
| timeout : true, | ||
| runtime : true, | ||
| watch : true, | ||
| arguments : false | ||
| }; | ||
| var validator = new zs({ strictMode : true }); | ||
| /** | ||
| * Weaver | ||
| * @class Weaver | ||
| * @constructor | ||
| */ | ||
| function Weaver () { | ||
| return this; | ||
| } | ||
| util.inherits(Weaver, events.EventEmitter); | ||
| var weaver | ||
| = module.exports | ||
| = new Weaver(); | ||
| function define (type, name, value, descriptor) { | ||
| descriptor = descriptor || {}; | ||
| descriptor.value = value; | ||
| descriptor.enumerable = true; | ||
| var target = descriptor.target || weaver; | ||
| if (!('writable' in descriptor)) { | ||
| descriptor.writable = true; | ||
| } | ||
| switch (type) { | ||
| case 'handler': | ||
| target.on(name, value); | ||
| return; | ||
| case 'method': | ||
| if (typeof value !== 'function') { | ||
| throw new Error('Method must be a function'); | ||
| } | ||
| descriptor.enumerable = false; | ||
| break; | ||
| case 'property': | ||
| break; | ||
| default: | ||
| throw new Error('Unsupported definition type ' + type); | ||
| } | ||
| Object.defineProperty(target, name, descriptor); | ||
| } | ||
| function expandEnv (env, executable) { | ||
| var expanded = Object.create(null); | ||
| expanded.HOME = process.env.HOME; | ||
| expanded.PATH = process.env.PATH; | ||
| if (!executable) { | ||
| expanded.NODE_PATH = process.env.NODE_PATH; | ||
| } | ||
| Object.keys(env) | ||
| .forEach(function (key) { | ||
| switch (env[key]) { | ||
| case true: | ||
| if (process.env.hasOwnProperty(key)) { | ||
| expanded[key] = process.env[key]; | ||
| } | ||
| break; | ||
| case false: | ||
| delete expanded[key]; | ||
| break; | ||
| default: | ||
| expanded[key] = env[key]; | ||
| } | ||
| }); | ||
| return expanded; | ||
| } | ||
| /** | ||
| * Weaver version | ||
| * @property version | ||
| * @type String | ||
| */ | ||
| define('property', 'version', require('../package').version, { writable: false }); | ||
| /** | ||
| * Start timestamp | ||
| * @property start | ||
| * @type Number | ||
| */ | ||
| define('property', 'start', Date.now(), { writable: false }); | ||
| /** | ||
| * Tasks by name | ||
| * @property tasks | ||
| * @type Object | ||
| */ | ||
| define('property', 'tasks', Object.create(null), { writable: false }); | ||
| /** | ||
| * Parsed configuration file | ||
| * @property config | ||
| * @type Object | ||
| */ | ||
| define('property', 'config', Object.create(null), { writable: false }); | ||
| /** | ||
| * Extend Weaver with property or method | ||
| * @method define | ||
| */ | ||
| define('method', 'define', define); | ||
| /** | ||
| * Update or create new task group with given options | ||
| * @method task | ||
| * @param {String} name | ||
| * @param {Object} options | ||
| */ | ||
| define('method', 'task', Task); | ||
| /** | ||
| * Write something to log | ||
| * @method log | ||
| */ | ||
| define('method', 'log', function () {}); | ||
| /** | ||
| * Validate configuration object | ||
| * @method validate | ||
| * @param {Object} configuration | ||
| * @return {Object} Valid configuration | ||
| */ | ||
| define('method', 'validate', function (configuration) { | ||
| var tasks = configuration.tasks; | ||
| /* Validate schema */ | ||
| assert.ok(validator.validate(configuration, schema), 'Invalid configuration'); | ||
| /* Perform additional validation */ | ||
| Object.keys(tasks).forEach(function (name) { | ||
| var task = tasks[name]; | ||
| /* Validate nested arrays for arguments */ | ||
| Object.keys(task).forEach(function (key) { | ||
| if (key === 'arguments') { | ||
| task[key].forEach(function (argument) { | ||
| if (Array.isArray(argument)) { | ||
| assert.equal( | ||
| task.count, argument.length, | ||
| 'Nested array in arguments should contain ' + task.count + ' values' | ||
| ); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| task.name = name; | ||
| }); | ||
| return configuration; | ||
| }); | ||
| /** | ||
| * Send SIGTERM to all processes and exit | ||
| * @method die | ||
| * @param {Number} code Exit code | ||
| */ | ||
| define('method', 'die', function (code) { | ||
| var that = this, | ||
| tasks = this.tasks, | ||
| timeout = 100, | ||
| name; | ||
| for (name in tasks) { | ||
| if (tasks[name].timeout > timeout) { | ||
| timeout = tasks[name].timeout; | ||
| } | ||
| tasks[name].persistent = false; | ||
| tasks[name].stopSubtasks(); | ||
| } | ||
| setTimeout(function () { | ||
| code = null == code? 1 : code; | ||
| that.log(sprintf('Terminated with code %u', code)); | ||
| setTimeout(function () { | ||
| process.exit(code); | ||
| }, 100); | ||
| }, timeout); | ||
| }); | ||
| /** | ||
| * Upgrade current state | ||
| * @method upgrade | ||
| * @param {String} data Configuration data | ||
| * @param {String} path Configuration path | ||
| */ | ||
| define('method', 'upgrade', function (data, path) { | ||
| var parts = [path], | ||
| params; | ||
| try { | ||
| /* Try to parse JSON */ | ||
| data = JSON.parse(data); | ||
| /* Validate new state */ | ||
| params = this.validate(data); | ||
| } catch (error) { | ||
| error.message = 'Config error: ' + error.message; | ||
| this.emit('error', error); | ||
| } | ||
| if (params) { | ||
| if (params.path) { | ||
| parts.push(params.path); | ||
| } | ||
| Object.keys(params.tasks) | ||
| .map(function (name) { | ||
| return params.tasks[name]; | ||
| }).forEach(function (task) { | ||
| task.cwd = resolve.apply(undefined, parts.concat(task.cwd || '.')); | ||
| }); | ||
| Object.keys(params.tasks) | ||
| .forEach(function (name) { | ||
| weaver.config[name] = params.tasks[name]; | ||
| }); | ||
| this.emit('upgrade'); | ||
| } | ||
| }); | ||
| /** | ||
| * Get status report | ||
| * @method status | ||
| * @return {Object} Task groups with subtasks data | ||
| */ | ||
| define('method', 'status', function () { | ||
| var tasks = this.tasks, | ||
| now = Date.now(), | ||
| result = {}, | ||
| i, l, name, task, subtask, subtasks; | ||
| for (name in tasks) { | ||
| result[name] = task = { | ||
| count : tasks[name].count, | ||
| source : tasks[name].source, | ||
| restart : tasks[name].restart, | ||
| subtasks : [] | ||
| }; | ||
| subtasks = tasks[name].subtasks; | ||
| for (i = 0, l = subtasks.length; i < l; i++) { | ||
| subtask = subtasks[i]; | ||
| task.subtasks.push(subtask? { | ||
| pid : subtask.pid, | ||
| args : subtask.args, | ||
| status : subtask.status, | ||
| uptime : now - subtask.start | ||
| } : null); | ||
| } | ||
| } | ||
| return result; | ||
| }); | ||
| /** | ||
| * Execute command with given arguments | ||
| * @method command | ||
| * @param {String} action Action name | ||
| * @param {String|Number} name Task group name or subtask pid | ||
| * @param {Array} args Arguments | ||
| */ | ||
| define('method', 'command', function (action, name, args) { | ||
| var tasks = this.tasks, | ||
| fn = Task.prototype[action + 'PID'], | ||
| task; | ||
| if (!Array.isArray(args)) { | ||
| args = []; | ||
| } | ||
| if (typeof fn !== 'function') { | ||
| throw new Error('Unknown action ' + action); | ||
| } | ||
| /* Execute command for all tasks */ | ||
| if (null == name) { | ||
| for (name in tasks) { | ||
| fn.apply(tasks[name], args); | ||
| } | ||
| } else { | ||
| task = tasks[name]; | ||
| if (task) { | ||
| if (action === 'kill') { | ||
| args.unshift(null); | ||
| } | ||
| fn.apply(task, args); | ||
| } else if (Number(name) == name) { | ||
| args.unshift(Number(name)); | ||
| this.command(action, null, args); | ||
| } else { | ||
| this.log('Task ' + name + ' was not found'); | ||
| } | ||
| } | ||
| }); | ||
| /** | ||
| * Fired when error occured | ||
| * @event error | ||
| * @param {Error} error Object with error details | ||
| */ | ||
| define('handler', 'error', function (error) { | ||
| this.log(error.message); | ||
| }); | ||
| /** | ||
| * Fired when tasks should be checked and upgraded | ||
| * @event upgrade | ||
| */ | ||
| define('handler', 'upgrade', function () { | ||
| var name; | ||
| for (name in this.config) { | ||
| /* Create or update task */ | ||
| this.task(name, this.config[name]); | ||
| } | ||
| }); | ||
| /* | ||
| * Task status codes | ||
| * R - restart | ||
| * E - error | ||
| * D - done (clean exit) | ||
| * W - work in progress | ||
| * S - stopped | ||
| */ | ||
| /** | ||
| * Task singleton (by name) | ||
| * @class Task | ||
| * @constructor | ||
| * @param {String} name Task group name | ||
| * @param {Object} options Task group configuration | ||
| */ | ||
| function Task (name, options) { | ||
| var task; | ||
| if (!(this instanceof Task)) { | ||
| return new Task(name, options); | ||
| } | ||
| task = weaver.tasks[name]; | ||
| if (task) { | ||
| task.upgrade(options); | ||
| return task; | ||
| } | ||
| task = weaver.tasks[name] = this; | ||
| /** | ||
| * Task name | ||
| * @property name | ||
| * @type String | ||
| */ | ||
| define('property', 'name', name, { | ||
| writable : false, | ||
| target : this | ||
| }); | ||
| /** | ||
| * Array with sub-process related data | ||
| * @property subtasks | ||
| * @type Array | ||
| */ | ||
| define('property', 'subtasks', [], { | ||
| writable : false, | ||
| target : this | ||
| }); | ||
| /** | ||
| * Watched patterns | ||
| * @property watch | ||
| * @type Array | ||
| */ | ||
| define('property', 'watch', [], { target: this }); | ||
| /** | ||
| * Watch callback | ||
| * @method watchHandler | ||
| */ | ||
| define('method', 'watchHandler', this.restartSubtasks.bind(this), { target: this }); | ||
| this.upgrade(options); | ||
| return this; | ||
| } | ||
| /** | ||
| * Timeout between SIGINT and SIGTERM for stop and restart | ||
| * @property timeout | ||
| * @type Number | ||
| * @default 1000 | ||
| */ | ||
| define('property', 'timeout', 1000, { target: Task.prototype }); | ||
| /** | ||
| * Minimal runtime required for persistent task | ||
| * to be restarted after unclean exit | ||
| * @property runtime | ||
| * @type Number | ||
| * @default 1000 | ||
| */ | ||
| define('property', 'runtime', 1000, { target: Task.prototype }); | ||
| /** | ||
| * Subtasks count | ||
| * @property count | ||
| * @type Number | ||
| * @default 0 | ||
| */ | ||
| define('property', 'count', 0, { target: Task.prototype }); | ||
| /** | ||
| * Source to execute in subtask | ||
| * @property source | ||
| * @type String | ||
| */ | ||
| define('property', 'source', '', { target: Task.prototype }); | ||
| /** | ||
| * Source file is executable | ||
| * @property executable | ||
| * @type Boolean | ||
| * @default false | ||
| */ | ||
| define('property', 'executable', false, { target: Task.prototype }); | ||
| /** | ||
| * Working directory for subtasks | ||
| * @property cwd | ||
| * @type String | ||
| */ | ||
| define('property', 'cwd', process.cwd(), { target: Task.prototype }); | ||
| /** | ||
| * Arguments for subtasks | ||
| * @property arguments | ||
| * @type Array | ||
| */ | ||
| define('property', 'arguments', [], { target: Task.prototype }); | ||
| /** | ||
| * Restart subtask on dirty exit | ||
| * @property persistent | ||
| * @type Boolean | ||
| * @default false | ||
| */ | ||
| define('property', 'persistent', false, { target: Task.prototype }); | ||
| /** | ||
| * Environment variables for subtasks | ||
| * @property env | ||
| * @type Object | ||
| */ | ||
| define('property', 'env', {}, { target: Task.prototype }); | ||
| /** | ||
| * Upgrade task group | ||
| * @method upgrade | ||
| * @param {Object} options Task group configuration | ||
| */ | ||
| define('method', 'upgrade', function (options) { | ||
| var that = this, | ||
| restart = false, | ||
| i, l, pid, subtask, key; | ||
| /* Upgrade parameters */ | ||
| Object.keys(mutable) | ||
| .forEach(function (key) { | ||
| try { | ||
| assert.deepEqual(that[key], options[key]); | ||
| } catch (change) { | ||
| that.upgradeParameter(key, options[key]); | ||
| restart = restart || !mutable[key]; | ||
| } | ||
| }); | ||
| /* Restart on demand */ | ||
| if (restart && this.subtasks.length) { | ||
| weaver.log(sprintf('Restart required for %s task group', this.name)); | ||
| this.restartSubtasks(); | ||
| } | ||
| /* Check count */ | ||
| for (i = 0, l = this.count; i < l; i++) { | ||
| subtask = this.subtasks[i]; | ||
| if (!subtask || (subtask.status === 'R' && !subtask.pid)) { | ||
| this.spawn(i); | ||
| } | ||
| } | ||
| /* Kill redundant */ | ||
| while (this.subtasks.length > this.count) { | ||
| this.stopSubtask(this.subtasks.pop()); | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Upgrade task parameter with value | ||
| * @method upgradeParameter | ||
| * @param {String} key Parameter name | ||
| * @param {Object} value Parameter value | ||
| */ | ||
| define('method', 'upgradeParameter', function (key, value) { | ||
| switch (key) { | ||
| case 'watch': | ||
| this.watch = value || []; | ||
| Watcher.stop(this.watchHandler); | ||
| Watcher.start(weaver, this.cwd, this.watch, this.watchHandler); | ||
| break; | ||
| default: | ||
| if (null == value) { | ||
| delete this[key]; | ||
| } else { | ||
| this[key] = value; | ||
| } | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Get subtask by pid | ||
| * @method get | ||
| * @param {Number} pid | ||
| * @return {Object} Subtask | ||
| */ | ||
| define('method', 'get', function (pid) { | ||
| var subtasks = this.subtasks, | ||
| subtask, i, l; | ||
| for (i = 0, l = subtasks.length; i < l; i++) { | ||
| subtask = subtasks[i]; | ||
| if (subtask && subtask.pid === pid) { | ||
| return subtask; | ||
| } | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Spawn subtask with given id | ||
| * @method spawn | ||
| * @param {Number} id | ||
| */ | ||
| define('method', 'spawn', function (id) { | ||
| var args = this.arguments || [], | ||
| binary = process.execPath, | ||
| subtask = { | ||
| id : id, | ||
| args : [], | ||
| status : 'W', | ||
| name : this.name, | ||
| start : Date.now(), | ||
| env : expandEnv(this.env, this.executable), | ||
| }, i, l, p1, p2, eargs; | ||
| /* Prepare arguments */ | ||
| for (i = 0, l = args.length; i < l; i++) { | ||
| if (Array.isArray(args[i])) { | ||
| subtask.args.push(args[i][id]); | ||
| } else { | ||
| subtask.args.push(args[i]); | ||
| } | ||
| } | ||
| eargs = subtask.args.slice(); | ||
| /* Prepare binary/source */ | ||
| if (this.executable) { | ||
| binary = this.source; | ||
| } else { | ||
| eargs.unshift(this.source); | ||
| } | ||
| /* Create new process */ | ||
| subtask.process = fork(binary, eargs, { | ||
| stdio : 'pipe', | ||
| cwd : resolve(this.cwd), | ||
| env : subtask.env | ||
| }); | ||
| /* Get subtask pid */ | ||
| subtask.pid = subtask.process.pid || 0; | ||
| if (subtask.pid) { | ||
| p1 = sprintf('%u (%s) ', subtask.pid, subtask.name); | ||
| p2 = sprintf('%u [%s] ', subtask.pid, subtask.name); | ||
| /* Setup logger */ | ||
| subtask.process.stdout.on('data', this.log.bind(this, p1)); | ||
| subtask.process.stderr.on('data', this.log.bind(this, p2)); | ||
| /* Setup exit handler */ | ||
| subtask.process.once('exit', this.exitHandler.bind(this, subtask)); | ||
| weaver.log(sprintf('Task %u (%s) spawned', subtask.pid, subtask.name)); | ||
| } else { | ||
| subtask.status = 'E'; | ||
| subtask.code = 255; | ||
| subtask.process.once('error', function (error) { | ||
| weaver.emit('error', error); | ||
| }); | ||
| weaver.log(sprintf('Failed to start task (%s)', subtask.name)); | ||
| } | ||
| this.subtasks[id] = subtask; | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Do something for each subtask | ||
| * @method foreach | ||
| * @param {Function} fn | ||
| * @param {Number|String} argument | ||
| */ | ||
| define('method', 'foreach', function (fn, argument) { | ||
| var subtasks = this.subtasks, | ||
| i, l; | ||
| for (i = 0, l = subtasks.length; i < l; i++) { | ||
| fn.call(this, subtasks[i], argument); | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Kill given subtask with signal | ||
| * @method killSubtask | ||
| * @param {Object} subtask | ||
| * @param {String} signal | ||
| */ | ||
| define('method', 'killSubtask', function (subtask, signal) { | ||
| if (subtask && subtask.pid) { | ||
| try { | ||
| subtask.process.kill(signal); | ||
| } catch (error) { | ||
| weaver.log(sprintf( | ||
| 'Failed to kill %u (%s) with %s', | ||
| subtask.pid, subtask.name, signal | ||
| )); | ||
| } | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Stop given subtask | ||
| * @method stopSubtask | ||
| * @param {Object} subtask | ||
| */ | ||
| define('method', 'stopSubtask', function (subtask) { | ||
| if (subtask && subtask.pid) { | ||
| subtask.process.kill('SIGINT'); | ||
| setTimeout(function () { | ||
| if (subtask.pid) { | ||
| subtask.process.kill('SIGTERM'); | ||
| } | ||
| }, this.timeout); | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Restart given subtask | ||
| * @method restartSubtask | ||
| * @param {Object} subtask | ||
| */ | ||
| define('method', 'restartSubtask', function (subtask) { | ||
| if (subtask) { | ||
| subtask.status = 'R'; | ||
| this.stopSubtask(subtask); | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Kill subtask with signal by PID | ||
| * @method killPID | ||
| * @param {Number} pid | ||
| * @param {String} signal | ||
| */ | ||
| define('method', 'killPID', function (pid, signal) { | ||
| if (null == pid) { | ||
| this.killSubtasks(signal); | ||
| } else { | ||
| this.killSubtask(this.get(pid), signal); | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Restart subtask by pid | ||
| * @method restartPID | ||
| * @param {Number} pid | ||
| */ | ||
| define('method', 'restartPID', function (pid) { | ||
| if (null == pid) { | ||
| this.restartSubtasks(); | ||
| } else { | ||
| this.restartSubtask(this.get(pid)); | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Stop subtask by pid | ||
| * @method restartPID | ||
| * @param {Number} pid | ||
| */ | ||
| define('method', 'stopPID', function (pid) { | ||
| if (null == pid) { | ||
| this.stopSubtasks(); | ||
| } else { | ||
| this.stopSubtask(this.get(pid)); | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Stop all subtasks | ||
| * @method stopSubtasks | ||
| */ | ||
| define('method', 'stopSubtasks', function () { | ||
| this.foreach(this.stopSubtask); | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Restart all subtasks | ||
| * @method restartSubtasks | ||
| */ | ||
| define('method', 'restartSubtasks', function () { | ||
| this.foreach(this.restartSubtask); | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Kill all subtasks with signal | ||
| * @method killSubtasks | ||
| * @param {String} signal | ||
| */ | ||
| define('method', 'killSubtasks', function (signal) { | ||
| this.foreach(this.killSubtask, signal); | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * Output data from buffer to weaver.log | ||
| * @method log | ||
| * @param {String} prefix | ||
| * @param {Buffer} data | ||
| */ | ||
| define('method', 'log', function (prefix, data) { | ||
| var messages = data.toString().split('\n'), | ||
| i, l; | ||
| for (i = 0, l = messages.length; i < l; i++) { | ||
| if (messages[i]) { | ||
| weaver.log.call(null, prefix + messages[i]); | ||
| } | ||
| } | ||
| }, { target: Task.prototype }); | ||
| /** | ||
| * @method exitHandler | ||
| * @param {Object} subtask | ||
| * @param {Number} code | ||
| * @param {String} signal | ||
| */ | ||
| define('method', 'exitHandler', function (subtask, code, signal) { | ||
| var restart = this.persistent, | ||
| elapsed; | ||
| if (code === null) { | ||
| weaver.log(sprintf( | ||
| 'Task %u (%s) was killed by %s', | ||
| subtask.pid, subtask.name, signal | ||
| )); | ||
| } else { | ||
| weaver.log(sprintf( | ||
| 'Task %u (%s) exited with code %u', | ||
| subtask.pid, subtask.name, code | ||
| )); | ||
| } | ||
| subtask.pid = 0; | ||
| subtask.code = code; | ||
| subtask.signal = signal; | ||
| delete subtask.process; | ||
| if (subtask.status !== 'R') { | ||
| if (code) { | ||
| subtask.status = 'E'; | ||
| } else if (signal) { | ||
| subtask.status = 'S'; | ||
| } else { | ||
| subtask.status = 'D'; | ||
| } | ||
| } | ||
| if (restart && code) { | ||
| elapsed = Date.now() - subtask.start; | ||
| if (elapsed < this.runtime) { | ||
| weaver.log(sprintf( | ||
| 'Restart skipped after %ums (%s)', | ||
| elapsed, subtask.name | ||
| )); | ||
| restart = false; | ||
| } | ||
| } | ||
| if (subtask.status === 'R') { | ||
| /* Restart was requested */ | ||
| restart = true; | ||
| } | ||
| if (!(this.name in weaver.config)) { | ||
| /* Task was dropped */ | ||
| restart = false; | ||
| if (!this.subtasks.filter(function (subtask) { return !!subtask.pid }).length) { | ||
| /* All subtasks were stopped */ | ||
| delete weaver.tasks[this.name]; | ||
| } | ||
| } | ||
| if (restart) { | ||
| this.spawn(subtask.id); | ||
| } | ||
| }, { target: Task.prototype }); |
| assert = require('assert') | ||
| weaver = require('../lib/weaver.js') | ||
| (require 'vows') | ||
| .describe('define') | ||
| .addBatch | ||
| 'property#writable': -> | ||
| name = "test_#{Math.random()}" | ||
| value = Math.random() | ||
| weaver.define 'property', name, value | ||
| assert name of weaver | ||
| assert.equal weaver[name], value | ||
| 'property#protected': -> | ||
| name = "test_#{Math.random()}" | ||
| value = Math.random() | ||
| weaver.define 'property', name, value, writable: false | ||
| assert name of weaver | ||
| assert.equal weaver[name], value | ||
| assert.throws -> | ||
| weaver.define 'property', name, 1 + value | ||
| 'method#parameters': -> | ||
| name = "test_#{Math.random()}" | ||
| assert.throws -> | ||
| weaver.define 'method', name, null | ||
| assert.throws -> | ||
| weaver.define 'method', name, {} | ||
| weaver.define 'method', name, -> | ||
| assert not weaver.propertyIsEnumerable name | ||
| assert.equal typeof weaver[name], 'function' | ||
| 'method#protected': -> | ||
| name = "test_#{Math.random()}" | ||
| weaver.define 'method', name, (->), writable: false | ||
| assert.throws -> | ||
| weaver.define 'method', name, -> | ||
| handler: -> | ||
| listeners = weaver.listeners('error').length | ||
| for i in [1 .. 5] | ||
| weaver.define 'handler', 'error', -> | ||
| assert.equal ++listeners, weaver.listeners('error').length | ||
| parameters: -> | ||
| assert.throws -> | ||
| weaver.define() | ||
| assert.throws -> | ||
| weaver.define 'event' | ||
| target: -> | ||
| name = "test_#{Math.random()}" | ||
| value = Math.random() | ||
| target = Object.create null | ||
| weaver.define 'property', name, value, target: target | ||
| assert not (name of weaver) | ||
| assert name of target | ||
| assert.equal target[name], value | ||
| .export(module) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
1
-50%32
10.34%0
-100%83494
-5.14%5
25%68
-92.3%+ Added
+ Added