pending-dns 1.2.4 → 1.3.0
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/.github/codeql/codeql-config.yml +11 -0
- package/.github/workflows/codeql.yml +52 -0
- package/.github/workflows/deploy.yml +16 -3
- package/.github/workflows/release.yaml +43 -0
- package/.github/workflows/test.yml +75 -0
- package/.release-please-manifest.json +3 -0
- package/CHANGELOG.md +8 -0
- package/CLAUDE.md +97 -0
- package/README.md +28 -5
- package/SECURITY.md +88 -0
- package/SECURITY.txt +27 -0
- package/bin/pending-dns.js +1 -1
- package/config/default.toml +13 -0
- package/config/test.toml +25 -0
- package/eslint.config.js +38 -0
- package/lib/api-server.js +13 -6
- package/lib/cached-resolver.js +5 -3
- package/lib/certs.js +11 -4
- package/lib/dns-handler.js +46 -14
- package/lib/dns-server.js +30 -18
- package/lib/dns-tcp-server.js +1 -1
- package/lib/dns-udp-server.js +1 -1
- package/lib/logger.js +3 -0
- package/lib/public-server.js +20 -2
- package/lib/sentry.js +72 -0
- package/lib/tools.js +1 -1
- package/lib/zone-store.js +4 -4
- package/package.json +43 -33
- package/release-please-config.json +13 -0
- package/server.js +5 -24
- package/systemd/pending-dns.service +4 -4
- package/test/api.test.js +139 -0
- package/test/cached-resolver.test.js +57 -0
- package/test/certs.test.js +34 -0
- package/test/dns-handler.test.js +140 -0
- package/test/dns-server.test.js +69 -0
- package/test/helpers.js +25 -0
- package/test/sentry.test.js +21 -0
- package/test/tools.test.js +48 -0
- package/test/zone-store.test.js +209 -0
- package/workers/api.js +3 -1
- package/workers/dns.js +2 -24
- package/workers/health.js +3 -26
- package/workers/public.js +3 -25
- package/.eslintrc +0 -14
- package/Gruntfile.js +0 -16
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
|
|
6
|
+
const { zoneStore, ZoneStore, allowedTypes, allowedTags } = require('../lib/zone-store');
|
|
7
|
+
const { flushTestDb, closeDb } = require('./helpers');
|
|
8
|
+
|
|
9
|
+
test.after(async () => {
|
|
10
|
+
await closeDb();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test.beforeEach(async () => {
|
|
14
|
+
await flushTestDb();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Pure helpers (no Redis)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
test('module exports the allowed record types and CAA tags', () => {
|
|
22
|
+
assert.deepEqual(allowedTypes, ['A', 'AAAA', 'ANAME', 'CNAME', 'MX', 'TXT', 'CAA', 'URL', 'NS']);
|
|
23
|
+
assert.deepEqual(allowedTags, ['issue', 'issuewild', 'iodef']);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('getFullId / parseFullId round-trip', () => {
|
|
27
|
+
const id = zoneStore.getFullId('com.example.www', 'CNAME', 'abc123');
|
|
28
|
+
const parsed = zoneStore.parseFullId(id);
|
|
29
|
+
assert.deepEqual(parsed, { name: 'com.example.www', type: 'CNAME', hid: 'abc123' });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('getFullId produces URL-safe ids (no +, /, =)', () => {
|
|
33
|
+
// Use values likely to produce base64 padding / special chars
|
|
34
|
+
const id = zoneStore.getFullId('com.example.subsubsub', 'AAAA', 'zzzzz');
|
|
35
|
+
assert.match(id, /^[A-Za-z0-9_-]+$/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('parseFullId returns empty object for garbage input', () => {
|
|
39
|
+
const parsed = zoneStore.parseFullId('!!!not base64!!!');
|
|
40
|
+
// name/type/hid will be undefined for malformed ids
|
|
41
|
+
assert.equal(parsed.hid, undefined);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('domainToName reverses labels and nameToDomain restores them', () => {
|
|
45
|
+
assert.equal(zoneStore.domainToName('www.example.com'), 'com.example.www');
|
|
46
|
+
assert.equal(zoneStore.nameToDomain('com.example.www'), 'www.example.com');
|
|
47
|
+
// round trip
|
|
48
|
+
assert.equal(zoneStore.nameToDomain(zoneStore.domainToName('a.b.c.example.com')), 'a.b.c.example.com');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('getsubdomain extracts the subdomain relative to a zone', () => {
|
|
52
|
+
assert.equal(zoneStore.getsubdomain('example.com', 'www.example.com'), 'www');
|
|
53
|
+
assert.equal(zoneStore.getsubdomain('example.com', 'a.b.example.com'), 'a.b');
|
|
54
|
+
assert.equal(zoneStore.getsubdomain('example.com', 'example.com'), '');
|
|
55
|
+
// unrelated domain returned as-is
|
|
56
|
+
assert.equal(zoneStore.getsubdomain('example.com', 'other.org'), 'other.org');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('formatValue shapes each record type for API output', () => {
|
|
60
|
+
const store = new ZoneStore();
|
|
61
|
+
|
|
62
|
+
assert.deepEqual(store.formatValue({ id: '1', type: 'A', value: ['1.2.3.4', false] }), {
|
|
63
|
+
id: '1',
|
|
64
|
+
type: 'A',
|
|
65
|
+
address: '1.2.3.4',
|
|
66
|
+
healthCheck: false
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
assert.deepEqual(store.formatValue({ id: '2', type: 'CNAME', zone: 'example.com', value: ['@'] }), {
|
|
70
|
+
id: '2',
|
|
71
|
+
type: 'CNAME',
|
|
72
|
+
target: 'example.com'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(store.formatValue({ id: '3', type: 'MX', value: ['mx.example.com', 10] }), {
|
|
76
|
+
id: '3',
|
|
77
|
+
type: 'MX',
|
|
78
|
+
exchange: 'mx.example.com',
|
|
79
|
+
priority: 10
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
assert.deepEqual(store.formatValue({ id: '4', type: 'CAA', value: ['letsencrypt.org', 'issue', 0] }), {
|
|
83
|
+
id: '4',
|
|
84
|
+
type: 'CAA',
|
|
85
|
+
value: 'letsencrypt.org',
|
|
86
|
+
tag: 'issue',
|
|
87
|
+
flags: 0
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
assert.deepEqual(store.formatValue({ id: '5', type: 'TXT', value: ['hello world'] }), {
|
|
91
|
+
id: '5',
|
|
92
|
+
type: 'TXT',
|
|
93
|
+
data: 'hello world'
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Redis-backed behaviour
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
test('add stores a record and list returns it', async () => {
|
|
102
|
+
const id = await zoneStore.add('example.com', '', 'A', ['1.2.3.4']);
|
|
103
|
+
assert.ok(id, 'add should return a record id');
|
|
104
|
+
|
|
105
|
+
const list = await zoneStore.list('example.com');
|
|
106
|
+
assert.equal(list.length, 1);
|
|
107
|
+
assert.equal(list[0].type, 'A');
|
|
108
|
+
assert.deepEqual(list[0].value, ['1.2.3.4']);
|
|
109
|
+
assert.equal(list[0].id, id);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('add rejects unknown types and empty values', async () => {
|
|
113
|
+
assert.equal(await zoneStore.add('example.com', '', 'BOGUS', ['x']), false);
|
|
114
|
+
assert.equal(await zoneStore.add('example.com', '', 'A', []), false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('resolveZone / resolveDomainZone find the closest zone', async () => {
|
|
118
|
+
await zoneStore.add('example.com', '', 'A', ['1.2.3.4']);
|
|
119
|
+
|
|
120
|
+
assert.equal(await zoneStore.resolveDomainZone('example.com'), 'example.com');
|
|
121
|
+
assert.equal(await zoneStore.resolveDomainZone('www.example.com'), 'example.com');
|
|
122
|
+
assert.equal(await zoneStore.resolveDomainZone('deep.sub.example.com'), 'example.com');
|
|
123
|
+
assert.equal(await zoneStore.resolveDomainZone('nonexistent-zone.org'), false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('resolve returns exact records', async () => {
|
|
127
|
+
await zoneStore.add('example.com', 'www', 'CNAME', ['example.com']);
|
|
128
|
+
const res = await zoneStore.resolve('www.example.com', 'CNAME', false);
|
|
129
|
+
assert.ok(Array.isArray(res));
|
|
130
|
+
assert.equal(res.length, 1);
|
|
131
|
+
assert.deepEqual(res[0].value, ['example.com']);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('resolve falls back to a one-level wildcard record', async () => {
|
|
135
|
+
await zoneStore.add('example.com', '*.test', 'CNAME', ['example.com']);
|
|
136
|
+
const res = await zoneStore.resolve('anything.test.example.com', 'CNAME', false);
|
|
137
|
+
assert.ok(Array.isArray(res));
|
|
138
|
+
assert.equal(res.length, 1);
|
|
139
|
+
assert.equal(res[0].wildcard, '*.test.example.com');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('update keeps the id when only the value changes', async () => {
|
|
143
|
+
const id = await zoneStore.add('example.com', '', 'A', ['1.2.3.4']);
|
|
144
|
+
const newId = await zoneStore.update('example.com', id, '', 'A', ['5.6.7.8']);
|
|
145
|
+
assert.equal(newId, id, 'id is stable when domain and type are unchanged');
|
|
146
|
+
|
|
147
|
+
const res = await zoneStore.resolve('example.com', 'A', false);
|
|
148
|
+
assert.deepEqual(res[0].value, ['5.6.7.8']);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('update changes the id when the type changes', async () => {
|
|
152
|
+
const id = await zoneStore.add('example.com', 'host', 'A', ['1.2.3.4']);
|
|
153
|
+
const newId = await zoneStore.update('example.com', id, 'host', 'AAAA', ['::1']);
|
|
154
|
+
assert.ok(newId);
|
|
155
|
+
assert.notEqual(newId, id);
|
|
156
|
+
|
|
157
|
+
// old A record is gone, new AAAA record exists
|
|
158
|
+
assert.equal(await zoneStore.resolve('host.example.com', 'A', false), false);
|
|
159
|
+
const res = await zoneStore.resolve('host.example.com', 'AAAA', false);
|
|
160
|
+
assert.deepEqual(res[0].value, ['::1']);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('del removes a single record by id', async () => {
|
|
164
|
+
const id = await zoneStore.add('example.com', '', 'A', ['1.2.3.4']);
|
|
165
|
+
assert.equal(await zoneStore.del('example.com', id), true);
|
|
166
|
+
assert.equal((await zoneStore.list('example.com')).length, 0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('deleting one of several same-type records keeps the others listed', async () => {
|
|
170
|
+
// Regression test: deleting one entry from a record hash that still has
|
|
171
|
+
// other entries must NOT drop the whole record key from the zone listing.
|
|
172
|
+
const id1 = await zoneStore.add('example.com', '', 'A', ['1.1.1.1']);
|
|
173
|
+
const id2 = await zoneStore.add('example.com', '', 'A', ['2.2.2.2']);
|
|
174
|
+
assert.ok(id1 && id2);
|
|
175
|
+
|
|
176
|
+
assert.equal((await zoneStore.list('example.com')).length, 2);
|
|
177
|
+
|
|
178
|
+
assert.equal(await zoneStore.del('example.com', id1), true);
|
|
179
|
+
|
|
180
|
+
const list = await zoneStore.list('example.com');
|
|
181
|
+
assert.equal(list.length, 1, 'the remaining A record must still be listed');
|
|
182
|
+
assert.deepEqual(list[0].value, ['2.2.2.2']);
|
|
183
|
+
|
|
184
|
+
// resolve must also still find it
|
|
185
|
+
const res = await zoneStore.resolve('example.com', 'A', false);
|
|
186
|
+
assert.ok(res && res.length === 1);
|
|
187
|
+
assert.deepEqual(res[0].value, ['2.2.2.2']);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('deleteDomain removes matching records and reports the count', async () => {
|
|
191
|
+
await zoneStore.add('example.com', 'multi', 'A', ['1.1.1.1']);
|
|
192
|
+
await zoneStore.add('example.com', 'multi', 'A', ['2.2.2.2']);
|
|
193
|
+
|
|
194
|
+
const deleted = await zoneStore.deleteDomain('multi.example.com', 'A');
|
|
195
|
+
assert.equal(deleted, 2);
|
|
196
|
+
assert.equal(await zoneStore.resolve('multi.example.com', 'A', false), false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('deleteDomain can match a specific value', async () => {
|
|
200
|
+
await zoneStore.add('example.com', 'pick', 'A', ['1.1.1.1']);
|
|
201
|
+
await zoneStore.add('example.com', 'pick', 'A', ['2.2.2.2']);
|
|
202
|
+
|
|
203
|
+
const deleted = await zoneStore.deleteDomain('pick.example.com', 'A', ['1.1.1.1']);
|
|
204
|
+
assert.equal(deleted, 1);
|
|
205
|
+
|
|
206
|
+
const res = await zoneStore.resolve('pick.example.com', 'A', false);
|
|
207
|
+
assert.equal(res.length, 1);
|
|
208
|
+
assert.deepEqual(res[0].value, ['2.2.2.2']);
|
|
209
|
+
});
|
package/workers/api.js
CHANGED
|
@@ -27,7 +27,7 @@ const closeProcess = (code, errType, err) => {
|
|
|
27
27
|
err
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
if (!logger.
|
|
30
|
+
if (!logger.errorReportingEnabled) {
|
|
31
31
|
setTimeout(() => process.exit(code), 10);
|
|
32
32
|
}
|
|
33
33
|
};
|
|
@@ -37,6 +37,8 @@ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', er
|
|
|
37
37
|
process.on('SIGTERM', () => closeProcess(0));
|
|
38
38
|
process.on('SIGINT', () => closeProcess(0));
|
|
39
39
|
|
|
40
|
+
require('../lib/sentry').initSentry(workerName);
|
|
41
|
+
|
|
40
42
|
const run = () => {
|
|
41
43
|
require(`../lib/${workerName}-server.js`)()
|
|
42
44
|
.then(() => {
|
package/workers/dns.js
CHANGED
|
@@ -27,7 +27,7 @@ const closeProcess = (code, errType, err) => {
|
|
|
27
27
|
err
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
if (!logger.
|
|
30
|
+
if (!logger.errorReportingEnabled) {
|
|
31
31
|
setTimeout(() => process.exit(code), 10);
|
|
32
32
|
}
|
|
33
33
|
};
|
|
@@ -37,29 +37,7 @@ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', er
|
|
|
37
37
|
process.on('SIGTERM', () => closeProcess(0));
|
|
38
38
|
process.on('SIGINT', () => closeProcess(0));
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const Bugsnag = require('@bugsnag/js');
|
|
42
|
-
if (process.env.BUGSNAG_API_KEY) {
|
|
43
|
-
Bugsnag.start({
|
|
44
|
-
apiKey: process.env.BUGSNAG_API_KEY,
|
|
45
|
-
appVersion: packageData.version,
|
|
46
|
-
logger: {
|
|
47
|
-
debug(...args) {
|
|
48
|
-
logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
49
|
-
},
|
|
50
|
-
info(...args) {
|
|
51
|
-
logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
52
|
-
},
|
|
53
|
-
warn(...args) {
|
|
54
|
-
logger.warn({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
55
|
-
},
|
|
56
|
-
error(...args) {
|
|
57
|
-
logger.error({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
62
|
-
}
|
|
40
|
+
require('../lib/sentry').initSentry(workerName);
|
|
63
41
|
|
|
64
42
|
const run = () => {
|
|
65
43
|
require(`../lib/${workerName}-server.js`)()
|
package/workers/health.js
CHANGED
|
@@ -27,7 +27,7 @@ const closeProcess = (code, errType, err) => {
|
|
|
27
27
|
err
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
if (!logger.
|
|
30
|
+
if (!logger.errorReportingEnabled) {
|
|
31
31
|
setTimeout(() => process.exit(code), 10);
|
|
32
32
|
}
|
|
33
33
|
};
|
|
@@ -37,30 +37,7 @@ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', er
|
|
|
37
37
|
process.on('SIGTERM', () => closeProcess(0));
|
|
38
38
|
process.on('SIGINT', () => closeProcess(0));
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const Bugsnag = require('@bugsnag/js');
|
|
42
|
-
|
|
43
|
-
if (process.env.BUGSNAG_API_KEY) {
|
|
44
|
-
Bugsnag.start({
|
|
45
|
-
apiKey: process.env.BUGSNAG_API_KEY,
|
|
46
|
-
appVersion: packageData.version,
|
|
47
|
-
logger: {
|
|
48
|
-
debug(...args) {
|
|
49
|
-
logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
50
|
-
},
|
|
51
|
-
info(...args) {
|
|
52
|
-
logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
53
|
-
},
|
|
54
|
-
warn(...args) {
|
|
55
|
-
logger.warn({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
56
|
-
},
|
|
57
|
-
error(...args) {
|
|
58
|
-
logger.error({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
63
|
-
}
|
|
40
|
+
require('../lib/sentry').initSentry(workerName);
|
|
64
41
|
|
|
65
42
|
const run = () => {
|
|
66
43
|
require(`../lib/${workerName}-worker.js`)()
|
|
@@ -111,6 +88,6 @@ if (cluster.isMaster) {
|
|
|
111
88
|
});
|
|
112
89
|
}
|
|
113
90
|
} else {
|
|
114
|
-
process.title = `
|
|
91
|
+
process.title = `pending-dns:${workerName}`;
|
|
115
92
|
run();
|
|
116
93
|
}
|
package/workers/public.js
CHANGED
|
@@ -27,7 +27,7 @@ const closeProcess = (code, errType, err) => {
|
|
|
27
27
|
err
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
if (!logger.
|
|
30
|
+
if (!logger.errorReportingEnabled) {
|
|
31
31
|
setTimeout(() => process.exit(code), 10);
|
|
32
32
|
}
|
|
33
33
|
};
|
|
@@ -37,29 +37,7 @@ process.on('unhandledRejection', err => closeProcess(2, 'unhandledRejection', er
|
|
|
37
37
|
process.on('SIGTERM', () => closeProcess(0));
|
|
38
38
|
process.on('SIGINT', () => closeProcess(0));
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
const Bugsnag = require('@bugsnag/js');
|
|
42
|
-
if (process.env.BUGSNAG_API_KEY) {
|
|
43
|
-
Bugsnag.start({
|
|
44
|
-
apiKey: process.env.BUGSNAG_API_KEY,
|
|
45
|
-
appVersion: packageData.version,
|
|
46
|
-
logger: {
|
|
47
|
-
debug(...args) {
|
|
48
|
-
logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
49
|
-
},
|
|
50
|
-
info(...args) {
|
|
51
|
-
logger.debug({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
52
|
-
},
|
|
53
|
-
warn(...args) {
|
|
54
|
-
logger.warn({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
55
|
-
},
|
|
56
|
-
error(...args) {
|
|
57
|
-
logger.error({ msg: args.shift(), worker: workerName, source: 'bugsnag', args: args.length ? args : undefined });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
logger.notifyError = Bugsnag.notify.bind(Bugsnag);
|
|
62
|
-
}
|
|
40
|
+
require('../lib/sentry').initSentry(workerName);
|
|
63
41
|
|
|
64
42
|
const run = () => {
|
|
65
43
|
require(`../lib/${workerName}-server.js`)()
|
|
@@ -110,6 +88,6 @@ if (cluster.isMaster) {
|
|
|
110
88
|
});
|
|
111
89
|
}
|
|
112
90
|
} else {
|
|
113
|
-
process.title = `
|
|
91
|
+
process.title = `pending-dns:${workerName}`;
|
|
114
92
|
run();
|
|
115
93
|
}
|
package/.eslintrc
DELETED
package/Gruntfile.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
module.exports = function (grunt) {
|
|
4
|
-
// Project configuration.
|
|
5
|
-
grunt.initConfig({
|
|
6
|
-
eslint: {
|
|
7
|
-
all: ['lib/**/*.js', 'server.js', 'routes/**/*.js', 'workers/**/*.js', 'Gruntfile.js']
|
|
8
|
-
}
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
// Load the plugin(s)
|
|
12
|
-
grunt.loadNpmTasks('grunt-eslint');
|
|
13
|
-
|
|
14
|
-
// Tasks
|
|
15
|
-
grunt.registerTask('default', ['eslint']);
|
|
16
|
-
};
|