node-schedule 1.2.5 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -25
- package/lib/nodeVersionUtils.js +30 -0
- package/lib/schedule.js +40 -14
- package/package.json +24 -10
- package/.eslintrc +0 -17
- package/.travis.yml +0 -15
- package/CONTRIBUTING.md +0 -31
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/node-schedule)
|
|
4
4
|
[](https://www.npmjs.com/package/node-schedule)
|
|
5
|
-
[](https://github.com/node-schedule/node-schedule/actions)
|
|
6
|
+
[](https://coveralls.io/r/node-schedule/node-schedule?branch=master)
|
|
6
7
|
[](https://gitter.im/node-schedule/node-schedule?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
7
8
|
|
|
8
9
|
[](https://nodei.co/npm/node-schedule/)
|
|
@@ -43,14 +44,19 @@ Every scheduled job in Node Schedule is represented by a `Job` object. You can
|
|
|
43
44
|
create jobs manually, then execute the `schedule()` method to apply a schedule,
|
|
44
45
|
or use the convenience function `scheduleJob()` as demonstrated below.
|
|
45
46
|
|
|
46
|
-
`Job` objects are `EventEmitter`
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
`Job` objects are `EventEmitter`s, and emit the following events:
|
|
48
|
+
* A `run` event after each execution.
|
|
49
|
+
* A `scheduled` event each time they're scheduled to run.
|
|
50
|
+
* A `canceled` event when an invocation is canceled before it's executed.
|
|
51
|
+
Note that `canceled` is the single-L American spelling.
|
|
52
|
+
* An `error` event when a job invocation triggered by a schedule throws or returns
|
|
53
|
+
a rejected `Promise`.
|
|
54
|
+
|
|
55
|
+
(Both the `scheduled` and `canceled` events receive a JavaScript date object as
|
|
56
|
+
a parameter).
|
|
57
|
+
Note that jobs are scheduled the first time immediately, so if you create a job
|
|
58
|
+
using the `scheduleJob()` convenience method, you'll miss the first `scheduled`
|
|
59
|
+
event, but you can query the invocation manually (see below).
|
|
54
60
|
|
|
55
61
|
### Cron-style Scheduling
|
|
56
62
|
|
|
@@ -58,7 +64,7 @@ The cron format consists of:
|
|
|
58
64
|
```
|
|
59
65
|
* * * * * *
|
|
60
66
|
┬ ┬ ┬ ┬ ┬ ┬
|
|
61
|
-
│ │ │ │ │
|
|
67
|
+
│ │ │ │ │ │
|
|
62
68
|
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
|
|
63
69
|
│ │ │ │ └───── month (1 - 12)
|
|
64
70
|
│ │ │ └────────── day of month (1 - 31)
|
|
@@ -89,11 +95,18 @@ var j = schedule.scheduleJob('0 17 ? * 0,4-6', function(){
|
|
|
89
95
|
|
|
90
96
|
Execute a cron job every 5 Minutes = */5 * * * *
|
|
91
97
|
|
|
98
|
+
You can also get when it is scheduled to run for every invocation of the job:
|
|
99
|
+
```js
|
|
100
|
+
var j = schedule.scheduleJob('0 1 * * *', function(fireDate){
|
|
101
|
+
console.log('This job was supposed to run at ' + fireDate + ', but actually ran at ' + new Date());
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
This is useful when you need to check if there is a delay of the job invocation when the system is busy, or save a record of all invocations of a job for audit purpose.
|
|
92
105
|
#### Unsupported Cron Features
|
|
93
106
|
|
|
94
|
-
Currently, `W` (nearest weekday)
|
|
95
|
-
|
|
96
|
-
|
|
107
|
+
Currently, `W` (nearest weekday) and `L` (last day of month/week) are not supported.
|
|
108
|
+
Most other features supported by popular cron implementations should work just fine,
|
|
109
|
+
including `#` (nth weekday of the month).
|
|
97
110
|
|
|
98
111
|
[cron-parser] is used to parse crontab instructions.
|
|
99
112
|
|
|
@@ -156,15 +169,31 @@ var j = schedule.scheduleJob(rule, function(){
|
|
|
156
169
|
});
|
|
157
170
|
```
|
|
158
171
|
|
|
172
|
+
Timezones are also supported. Here is an example of executing at the start of every day in the UTC timezone.
|
|
173
|
+
|
|
174
|
+
```js
|
|
175
|
+
var rule = new schedule.RecurrenceRule();
|
|
176
|
+
rule.hour = 0;
|
|
177
|
+
rule.tz = 'Etc/UTC';
|
|
178
|
+
|
|
179
|
+
var j = schedule.scheduleJob(rule, function(){
|
|
180
|
+
console.log('A new day has begun in the UTC timezone!');
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
A list of acceptable tz (timezone) values can be found at <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
|
|
185
|
+
|
|
159
186
|
#### RecurrenceRule properties
|
|
160
187
|
|
|
161
|
-
- `second`
|
|
162
|
-
- `minute`
|
|
163
|
-
- `hour`
|
|
164
|
-
- `date`
|
|
165
|
-
- `month`
|
|
188
|
+
- `second (0-59)`
|
|
189
|
+
- `minute (0-59)`
|
|
190
|
+
- `hour (0-23)`
|
|
191
|
+
- `date (1-31)`
|
|
192
|
+
- `month (0-11)`
|
|
166
193
|
- `year`
|
|
167
|
-
- `dayOfWeek`
|
|
194
|
+
- `dayOfWeek (0-6) Starting with Sunday`
|
|
195
|
+
- `tz`
|
|
196
|
+
|
|
168
197
|
|
|
169
198
|
> **Note**: It's worth noting that the default value of a component of a recurrence rule is
|
|
170
199
|
> `null` (except for second, which is 0 for familiarity with cron). *If we did not
|
|
@@ -197,11 +226,11 @@ var j = schedule.scheduleJob({ start: startTime, end: endTime, rule: '*/1 * * *
|
|
|
197
226
|
|
|
198
227
|
### Handle Jobs and Job Invocations
|
|
199
228
|
|
|
200
|
-
There are some function to get
|
|
229
|
+
There are some function to get information for a Job and to handle the Job and
|
|
201
230
|
Invocations.
|
|
202
231
|
|
|
203
232
|
|
|
204
|
-
#### job.cancel(
|
|
233
|
+
#### job.cancel(reschedule)
|
|
205
234
|
You can invalidate any job with the `cancel()` method:
|
|
206
235
|
|
|
207
236
|
```js
|
|
@@ -210,7 +239,7 @@ j.cancel();
|
|
|
210
239
|
All planned invocations will be canceled. When you set the parameter ***reschedule***
|
|
211
240
|
to true then the Job is newly scheduled afterwards.
|
|
212
241
|
|
|
213
|
-
#### job.cancelNext(
|
|
242
|
+
#### job.cancelNext(reschedule)
|
|
214
243
|
This method invalidates the next planned invocation or the job.
|
|
215
244
|
When you set the parameter ***reschedule*** to true then the Job is newly scheduled
|
|
216
245
|
afterwards.
|
|
@@ -224,8 +253,9 @@ This method returns a Date object for the planned next Invocation for this Job.
|
|
|
224
253
|
|
|
225
254
|
## Contributing
|
|
226
255
|
|
|
227
|
-
This module was originally developed by [Matt Patenaude]
|
|
228
|
-
|
|
256
|
+
This module was originally developed by [Matt Patenaude] who eventually passed over maintainer's mantle over to [Tejas Manohar].
|
|
257
|
+
|
|
258
|
+
Currently it is being maintained by [Igor Savin] and [our amazing community].
|
|
229
259
|
|
|
230
260
|
We'd love to get your contributions. Individuals making significant and valuable
|
|
231
261
|
contributions are given commit-access to the project to contribute as they see fit.
|
|
@@ -243,7 +273,8 @@ Licensed under the **[MIT License] [license]**.
|
|
|
243
273
|
[Contributing]: https://github.com/node-schedule/node-schedule/blob/master/CONTRIBUTING.md
|
|
244
274
|
[Matt Patenaude]: https://github.com/mattpat
|
|
245
275
|
[Tejas Manohar]: http://tejas.io
|
|
276
|
+
[Igor Savin]: https://twitter.com/kibertoad
|
|
246
277
|
[license]: https://github.com/node-schedule/node-schedule/blob/master/LICENSE
|
|
247
278
|
[Tejas Manohar]: https://github.com/tejasmanohar
|
|
248
|
-
[
|
|
279
|
+
[our amazing community]: https://github.com/node-schedule/node-schedule/graphs/contributors
|
|
249
280
|
[cron-parser]: https://github.com/harrisiirak/cron-parser
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
var nodeVersionString = process.versions.node
|
|
4
|
+
|
|
5
|
+
function getNodeVersion(nodeVersionString) {
|
|
6
|
+
var versionSplit = nodeVersionString.split('.')
|
|
7
|
+
return {
|
|
8
|
+
majorVersion: Number(versionSplit[0]),
|
|
9
|
+
minorVersion: Number(versionSplit[1])
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function arePromisesSupported(nodeVersion) {
|
|
14
|
+
var majorVersion = nodeVersion.majorVersion
|
|
15
|
+
var minorVersion = nodeVersion.minorVersion
|
|
16
|
+
|
|
17
|
+
if (majorVersion > 0) {
|
|
18
|
+
return true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (minorVersion < 12) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports.nodeVersionString = nodeVersionString;
|
|
29
|
+
module.exports.arePromisesSupported = arePromisesSupported;
|
|
30
|
+
module.exports.getNodeVersion = getNodeVersion;
|
package/lib/schedule.js
CHANGED
|
@@ -11,11 +11,13 @@ var events = require('events'),
|
|
|
11
11
|
cronParser = require('cron-parser'),
|
|
12
12
|
CronDate = require('cron-parser/lib/date'),
|
|
13
13
|
lt = require('long-timeout'),
|
|
14
|
-
sorted = require('sorted-array-functions')
|
|
14
|
+
sorted = require('sorted-array-functions'),
|
|
15
|
+
nodeVersionUtils = require('./nodeVersionUtils')
|
|
15
16
|
|
|
16
17
|
/* Job object */
|
|
17
18
|
var anonJobCounter = 0;
|
|
18
19
|
var scheduledJobs = {};
|
|
20
|
+
var promisesSupported = nodeVersionUtils.arePromisesSupported(nodeVersionUtils.getNodeVersion(nodeVersionUtils.nodeVersionString))
|
|
19
21
|
|
|
20
22
|
function isValidDate(date) {
|
|
21
23
|
// Taken from http://stackoverflow.com/a/12372720/1562178
|
|
@@ -90,7 +92,7 @@ function Job(name, job, callback) {
|
|
|
90
92
|
|
|
91
93
|
cancelInvocation(inv);
|
|
92
94
|
|
|
93
|
-
if (reschedule && inv.recurrenceRule.recurs) {
|
|
95
|
+
if (reschedule && (inv.recurrenceRule.recurs || inv.recurrenceRule.next)) {
|
|
94
96
|
newInv = scheduleNextRecurrence(inv.recurrenceRule, this, inv.fireDate, inv.endDate);
|
|
95
97
|
if (newInv !== null) {
|
|
96
98
|
newInvs.push(newInv);
|
|
@@ -125,7 +127,7 @@ function Job(name, job, callback) {
|
|
|
125
127
|
|
|
126
128
|
cancelInvocation(nextInv);
|
|
127
129
|
|
|
128
|
-
if (reschedule && nextInv.recurrenceRule.recurs) {
|
|
130
|
+
if (reschedule && (nextInv.recurrenceRule.recurs || nextInv.recurrenceRule.next)) {
|
|
129
131
|
newInv = scheduleNextRecurrence(nextInv.recurrenceRule, this, nextInv.fireDate, nextInv.endDate);
|
|
130
132
|
if (newInv !== null) {
|
|
131
133
|
this.trackInvocation(newInv);
|
|
@@ -167,12 +169,12 @@ function Job(name, job, callback) {
|
|
|
167
169
|
|
|
168
170
|
util.inherits(Job, events.EventEmitter);
|
|
169
171
|
|
|
170
|
-
Job.prototype.invoke = function() {
|
|
172
|
+
Job.prototype.invoke = function(fireDate) {
|
|
171
173
|
if (typeof this.job == 'function') {
|
|
172
174
|
this.setTriggeredJobs(this.triggeredJobs() + 1);
|
|
173
|
-
this.job();
|
|
175
|
+
return this.job(fireDate);
|
|
174
176
|
} else {
|
|
175
|
-
this.job.execute();
|
|
177
|
+
this.job.execute(fireDate);
|
|
176
178
|
}
|
|
177
179
|
};
|
|
178
180
|
|
|
@@ -188,10 +190,14 @@ Job.prototype.schedule = function(spec) {
|
|
|
188
190
|
var end;
|
|
189
191
|
var tz;
|
|
190
192
|
|
|
193
|
+
// save passed-in value before 'spec' is replaced
|
|
194
|
+
if (typeof spec === 'object' && 'tz' in spec) {
|
|
195
|
+
tz = spec.tz;
|
|
196
|
+
}
|
|
197
|
+
|
|
191
198
|
if (typeof spec === 'object' && spec.rule) {
|
|
192
199
|
start = spec.start || undefined;
|
|
193
200
|
end = spec.end || undefined;
|
|
194
|
-
tz = spec.tz;
|
|
195
201
|
spec = spec.rule;
|
|
196
202
|
|
|
197
203
|
if (start) {
|
|
@@ -398,6 +404,11 @@ RecurrenceRule.prototype.isValid = function() {
|
|
|
398
404
|
};
|
|
399
405
|
|
|
400
406
|
RecurrenceRule.prototype.nextInvocationDate = function(base) {
|
|
407
|
+
var next = this._nextInvocationDate(base);
|
|
408
|
+
return next ? next.toDate() : null;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
RecurrenceRule.prototype._nextInvocationDate = function(base) {
|
|
401
412
|
base = ((base instanceof CronDate) || (base instanceof Date)) ? base : (new Date());
|
|
402
413
|
if (!this.recurs) {
|
|
403
414
|
return null;
|
|
@@ -464,7 +475,7 @@ RecurrenceRule.prototype.nextInvocationDate = function(base) {
|
|
|
464
475
|
break;
|
|
465
476
|
}
|
|
466
477
|
|
|
467
|
-
return next
|
|
478
|
+
return next;
|
|
468
479
|
};
|
|
469
480
|
|
|
470
481
|
function recurMatch(val, matcher) {
|
|
@@ -491,10 +502,15 @@ function recurMatch(val, matcher) {
|
|
|
491
502
|
|
|
492
503
|
/* Date-based scheduler */
|
|
493
504
|
function runOnDate(date, job) {
|
|
494
|
-
var now =
|
|
505
|
+
var now = Date.now();
|
|
495
506
|
var then = date.getTime();
|
|
496
|
-
|
|
497
|
-
return lt.setTimeout(
|
|
507
|
+
|
|
508
|
+
return lt.setTimeout(function() {
|
|
509
|
+
if (then > Date.now())
|
|
510
|
+
runOnDate(date, job);
|
|
511
|
+
else
|
|
512
|
+
job();
|
|
513
|
+
}, (then < now ? 0 : then - now));
|
|
498
514
|
}
|
|
499
515
|
|
|
500
516
|
var invocations = [];
|
|
@@ -535,8 +551,18 @@ function prepareNextInvocation() {
|
|
|
535
551
|
|
|
536
552
|
job.stopTrackingInvocation(cinv);
|
|
537
553
|
|
|
538
|
-
|
|
539
|
-
|
|
554
|
+
try {
|
|
555
|
+
var result = job.invoke(cinv.fireDate instanceof CronDate ? cinv.fireDate.toDate() : cinv.fireDate);
|
|
556
|
+
job.emit('run');
|
|
557
|
+
|
|
558
|
+
if (promisesSupported && result instanceof Promise) {
|
|
559
|
+
result.catch(function (err) {
|
|
560
|
+
job.emit('error', err);
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
} catch (err) {
|
|
564
|
+
job.emit('error', err);
|
|
565
|
+
}
|
|
540
566
|
});
|
|
541
567
|
}
|
|
542
568
|
}
|
|
@@ -569,7 +595,7 @@ function scheduleNextRecurrence(rule, job, prevDate, endDate) {
|
|
|
569
595
|
|
|
570
596
|
prevDate = (prevDate instanceof CronDate) ? prevDate : new CronDate();
|
|
571
597
|
|
|
572
|
-
var date = (rule instanceof RecurrenceRule) ? rule.
|
|
598
|
+
var date = (rule instanceof RecurrenceRule) ? rule._nextInvocationDate(prevDate) : rule.next();
|
|
573
599
|
if (date === null) {
|
|
574
600
|
return null;
|
|
575
601
|
}
|
package/package.json
CHANGED
|
@@ -1,38 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-schedule",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "A cron-like and not-cron-like job scheduler for Node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"schedule",
|
|
7
7
|
"task",
|
|
8
8
|
"job",
|
|
9
|
-
"cron"
|
|
9
|
+
"cron",
|
|
10
|
+
"in-memory"
|
|
10
11
|
],
|
|
11
12
|
"license": "MIT",
|
|
12
13
|
"main": "./lib/schedule.js",
|
|
13
14
|
"scripts": {
|
|
14
15
|
"test": "nodeunit",
|
|
15
|
-
"
|
|
16
|
+
"test:coverage": "istanbul cover ./node_modules/.bin/nodeunit test",
|
|
17
|
+
"test:coverage-win": "istanbul cover ./node_modules/nodeunit/bin/nodeunit test",
|
|
18
|
+
"lint": "eslint lib test",
|
|
19
|
+
"lint:fix": "eslint --fix lib test"
|
|
16
20
|
},
|
|
17
21
|
"author": {
|
|
18
22
|
"name": "Matt Patenaude",
|
|
19
23
|
"email": "matt@mattpatenaude.com",
|
|
20
24
|
"url": "http://mattpatenaude.com"
|
|
21
25
|
},
|
|
26
|
+
"contributors": [
|
|
27
|
+
{
|
|
28
|
+
"name": "Igor Savin",
|
|
29
|
+
"email": "kibertoad@gmail.com"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
22
32
|
"repository": {
|
|
23
33
|
"type": "git",
|
|
24
34
|
"url": "https://github.com/node-schedule/node-schedule.git"
|
|
25
35
|
},
|
|
26
36
|
"dependencies": {
|
|
27
|
-
"cron-parser": "^2.
|
|
37
|
+
"cron-parser": "^2.18.0",
|
|
28
38
|
"long-timeout": "0.1.1",
|
|
29
|
-
"sorted-array-functions": "^1.
|
|
39
|
+
"sorted-array-functions": "^1.3.0"
|
|
30
40
|
},
|
|
31
41
|
"devDependencies": {
|
|
32
|
-
"
|
|
33
|
-
"eslint": "^3.19.0",
|
|
42
|
+
"eslint": "^7.18.0",
|
|
34
43
|
"istanbul": "^0.4.5",
|
|
35
|
-
"nodeunit": "
|
|
36
|
-
"sinon": "^
|
|
37
|
-
}
|
|
44
|
+
"nodeunit": "~0.10.2",
|
|
45
|
+
"sinon": "^2.4.1"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"README.md",
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"lib/*"
|
|
51
|
+
]
|
|
38
52
|
}
|
package/.eslintrc
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"node": true,
|
|
4
|
-
"es6": true
|
|
5
|
-
},
|
|
6
|
-
"rules": {
|
|
7
|
-
"eqeqeq": ["error", "smart"],
|
|
8
|
-
"indent": ["error", 2, { "SwitchCase": 1 }],
|
|
9
|
-
"no-constant-condition": "off",
|
|
10
|
-
"no-redeclare": "warn",
|
|
11
|
-
"no-underscore-dangle": "off",
|
|
12
|
-
"no-use-before-define": ["warn","nofunc"],
|
|
13
|
-
"quotes": ["error", "single"],
|
|
14
|
-
"space-before-blocks": "error",
|
|
15
|
-
"strict": "error"
|
|
16
|
-
}
|
|
17
|
-
}
|
package/.travis.yml
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
language: node_js
|
|
2
|
-
node_js:
|
|
3
|
-
- "0.12"
|
|
4
|
-
- "0.10"
|
|
5
|
-
- "4"
|
|
6
|
-
- "6"
|
|
7
|
-
- "7"
|
|
8
|
-
before_script: >
|
|
9
|
-
if nvm ls-remote --lts | grep "$(nvm current)"; then
|
|
10
|
-
echo "running on a LTS node version, linting"
|
|
11
|
-
npm test
|
|
12
|
-
else
|
|
13
|
-
echo "running on a non-LTS node version, skipping linting"
|
|
14
|
-
fi
|
|
15
|
-
script: ./node_modules/.bin/istanbul cover ./node_modules/.bin/nodeunit test && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage
|
package/CONTRIBUTING.md
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
## Rules
|
|
2
|
-
1. **No `--force` pushes** or modifying the git history in any way
|
|
3
|
-
2. Follow existing code style
|
|
4
|
-
3. Pull requests with tests are much more likely to be accepted
|
|
5
|
-
4. Follow the guidelines below
|
|
6
|
-
|
|
7
|
-
## Bugfix or Feature?
|
|
8
|
-
|
|
9
|
-
This project uses the [gitflow workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). Simply put, you need to decide if your contribution will be a bug fix that could be released as a patch, or a feature that will end up being a minor or major release.
|
|
10
|
-
|
|
11
|
-
### Found a bug that can be fixed without affecting the API?
|
|
12
|
-
|
|
13
|
-
1. **Fork** this repo
|
|
14
|
-
2. Create a new branch from `master` to work in
|
|
15
|
-
3. **Add tests** if needed
|
|
16
|
-
4. Make sure your code **lints** by running `npm run lint`
|
|
17
|
-
5. Make sure your code **passes tests** by running `npm test`
|
|
18
|
-
6. Submit a **pull request** against the `master` branch
|
|
19
|
-
|
|
20
|
-
### New feature or anything that would result in a change to the API?
|
|
21
|
-
|
|
22
|
-
1. **Fork** this repo
|
|
23
|
-
2. Create a new branch from `develop` to work in
|
|
24
|
-
3. **Add tests** to as needed
|
|
25
|
-
4. Make sure your code **lints** by running `npm run lint`
|
|
26
|
-
5. Make sure your code **passes tests** by running `npm test`
|
|
27
|
-
6. Submit a **pull request** against the `develop` branch
|
|
28
|
-
|
|
29
|
-
## Releases
|
|
30
|
-
|
|
31
|
-
Declaring formal releases remains the prerogative of the project maintainer.
|