bpmnlint-plugin-camunda-compat 2.22.0 → 2.23.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/LICENSE +20 -20
- package/README.md +39 -39
- package/index.js +237 -231
- package/package.json +53 -53
- package/rules/camunda-cloud/called-element.js +42 -42
- package/rules/camunda-cloud/collapsed-subprocess.js +40 -40
- package/rules/camunda-cloud/connector-properties/config.js +90 -90
- package/rules/camunda-cloud/connector-properties/index.js +47 -47
- package/rules/camunda-cloud/duplicate-execution-listeners.js +33 -33
- package/rules/camunda-cloud/duplicate-task-headers.js +58 -58
- package/rules/camunda-cloud/element-type/config.js +66 -66
- package/rules/camunda-cloud/element-type/index.js +133 -133
- package/rules/camunda-cloud/error-reference.js +71 -71
- package/rules/camunda-cloud/escalation-boundary-event-attached-to-ref.js +48 -48
- package/rules/camunda-cloud/escalation-reference.js +66 -66
- package/rules/camunda-cloud/event-based-gateway-target.js +38 -38
- package/rules/camunda-cloud/executable-process.js +61 -61
- package/rules/camunda-cloud/execution-listener.js +34 -34
- package/rules/camunda-cloud/feel.js +82 -82
- package/rules/camunda-cloud/implementation/config.js +16 -16
- package/rules/camunda-cloud/implementation/index.js +218 -218
- package/rules/camunda-cloud/inclusive-gateway.js +35 -35
- package/rules/camunda-cloud/link-event.js +142 -142
- package/rules/camunda-cloud/loop-characteristics.js +66 -66
- package/rules/camunda-cloud/message-reference.js +60 -60
- package/rules/camunda-cloud/no-binding-type.js +44 -0
- package/rules/camunda-cloud/no-candidate-users.js +38 -38
- package/rules/camunda-cloud/no-execution-listeners.js +21 -21
- package/rules/camunda-cloud/no-expression.js +173 -173
- package/rules/camunda-cloud/no-loop.js +316 -316
- package/rules/camunda-cloud/no-multiple-none-start-events.js +41 -41
- package/rules/camunda-cloud/no-propagate-all-parent-variables.js +44 -44
- package/rules/camunda-cloud/no-signal-event-sub-process.js +45 -45
- package/rules/camunda-cloud/no-task-schedule.js +18 -18
- package/rules/camunda-cloud/no-template.js +23 -23
- package/rules/camunda-cloud/no-zeebe-properties.js +18 -18
- package/rules/camunda-cloud/no-zeebe-user-task.js +27 -27
- package/rules/camunda-cloud/secrets.js +119 -119
- package/rules/camunda-cloud/sequence-flow-condition.js +56 -56
- package/rules/camunda-cloud/signal-reference.js +64 -64
- package/rules/camunda-cloud/start-event-form.js +97 -97
- package/rules/camunda-cloud/subscription.js +65 -65
- package/rules/camunda-cloud/task-schedule.js +67 -67
- package/rules/camunda-cloud/timer/config.js +46 -46
- package/rules/camunda-cloud/timer/index.js +183 -183
- package/rules/camunda-cloud/user-task-definition.js +24 -24
- package/rules/camunda-cloud/user-task-form.js +142 -142
- package/rules/camunda-cloud/wait-for-completion.js +46 -46
- package/rules/camunda-platform/history-time-to-live.js +19 -19
- package/rules/utils/cron.js +95 -95
- package/rules/utils/element.js +533 -533
- package/rules/utils/error-types.js +25 -25
- package/rules/utils/iso8601.js +52 -52
- package/rules/utils/reporter.js +37 -37
- package/rules/utils/rule.js +46 -46
- package/rules/utils/version.js +4 -4
@@ -1,142 +1,142 @@
|
|
1
|
-
const { is } = require('bpmnlint-utils');
|
2
|
-
|
3
|
-
const {
|
4
|
-
findExtensionElement,
|
5
|
-
findExtensionElements,
|
6
|
-
findParent,
|
7
|
-
hasProperties,
|
8
|
-
hasProperty
|
9
|
-
} = require('../utils/element');
|
10
|
-
|
11
|
-
const { reportErrors } = require('../utils/reporter');
|
12
|
-
|
13
|
-
const { skipInNonExecutableProcess } = require('../utils/rule');
|
14
|
-
|
15
|
-
const { greaterOrEqual } = require('../utils/version');
|
16
|
-
|
17
|
-
const formIdAllowedVersions = {
|
18
|
-
desktop: '8.4',
|
19
|
-
web: '8.0'
|
20
|
-
};
|
21
|
-
|
22
|
-
const ZEEBE_USER_TASK_VERSION = '8.5';
|
23
|
-
|
24
|
-
module.exports = skipInNonExecutableProcess(function({ modeler = 'desktop', version }) {
|
25
|
-
function check(node, reporter) {
|
26
|
-
if (!is(node, 'bpmn:UserTask')) {
|
27
|
-
return;
|
28
|
-
}
|
29
|
-
|
30
|
-
const formDefinition = findExtensionElement(node, 'zeebe:FormDefinition');
|
31
|
-
|
32
|
-
if (!formDefinition) {
|
33
|
-
return;
|
34
|
-
}
|
35
|
-
|
36
|
-
// handle Zebee User Task
|
37
|
-
if (isZeebeUserTask(node)) {
|
38
|
-
|
39
|
-
// handled by no-zeebe-user-task rule
|
40
|
-
if (!greaterOrEqual(version, ZEEBE_USER_TASK_VERSION)) {
|
41
|
-
return;
|
42
|
-
}
|
43
|
-
|
44
|
-
const errors = hasProperty(formDefinition, [
|
45
|
-
'externalReference',
|
46
|
-
'formId'
|
47
|
-
], node) || [];
|
48
|
-
|
49
|
-
if (errors.length) {
|
50
|
-
reportErrors(node, reporter, errors);
|
51
|
-
}
|
52
|
-
|
53
|
-
return;
|
54
|
-
}
|
55
|
-
|
56
|
-
let errors;
|
57
|
-
|
58
|
-
const formIdAllowedVersion = formIdAllowedVersions[ modeler ];
|
59
|
-
|
60
|
-
if (isFormIdAllowed(version, formIdAllowedVersion)) {
|
61
|
-
errors = hasProperty(formDefinition, [
|
62
|
-
'formKey',
|
63
|
-
'formId'
|
64
|
-
], node);
|
65
|
-
} else {
|
66
|
-
errors = hasProperties(formDefinition, {
|
67
|
-
formId: {
|
68
|
-
allowed: false,
|
69
|
-
allowedVersion: formIdAllowedVersion
|
70
|
-
}
|
71
|
-
}, node);
|
72
|
-
|
73
|
-
if (errors.length) {
|
74
|
-
reportErrors(node, reporter, errors);
|
75
|
-
|
76
|
-
return;
|
77
|
-
}
|
78
|
-
|
79
|
-
errors = hasProperties(formDefinition, {
|
80
|
-
formKey: {
|
81
|
-
required: true
|
82
|
-
}
|
83
|
-
}, node);
|
84
|
-
}
|
85
|
-
|
86
|
-
if (errors.length) {
|
87
|
-
reportErrors(node, reporter, errors);
|
88
|
-
|
89
|
-
return;
|
90
|
-
}
|
91
|
-
|
92
|
-
const formKey = formDefinition.get('formKey');
|
93
|
-
|
94
|
-
const userTaskForm = findUserTaskForm(node, formKey);
|
95
|
-
|
96
|
-
if (!userTaskForm) {
|
97
|
-
return;
|
98
|
-
}
|
99
|
-
|
100
|
-
errors = hasProperties(userTaskForm, {
|
101
|
-
body: {
|
102
|
-
required: true
|
103
|
-
}
|
104
|
-
}, node);
|
105
|
-
|
106
|
-
if (errors.length) {
|
107
|
-
reportErrors(node, reporter, errors);
|
108
|
-
}
|
109
|
-
}
|
110
|
-
|
111
|
-
return {
|
112
|
-
check
|
113
|
-
};
|
114
|
-
});
|
115
|
-
|
116
|
-
// helpers //////////
|
117
|
-
|
118
|
-
function findUserTaskForm(node, formKey) {
|
119
|
-
const process = findParent(node, 'bpmn:Process');
|
120
|
-
|
121
|
-
if (!process) {
|
122
|
-
return;
|
123
|
-
}
|
124
|
-
|
125
|
-
const userTaskForms = findExtensionElements(process, 'zeebe:UserTaskForm');
|
126
|
-
|
127
|
-
if (userTaskForms && userTaskForms.length) {
|
128
|
-
return userTaskForms.find(userTaskForm => {
|
129
|
-
const id = userTaskForm.get('id');
|
130
|
-
|
131
|
-
return `camunda-forms:bpmn:${ id }` === formKey;
|
132
|
-
});
|
133
|
-
}
|
134
|
-
}
|
135
|
-
|
136
|
-
function isFormIdAllowed(version, formIdAllowedVersion) {
|
137
|
-
return greaterOrEqual(version, formIdAllowedVersion);
|
138
|
-
}
|
139
|
-
|
140
|
-
function isZeebeUserTask(node) {
|
141
|
-
return !!findExtensionElement(node, 'zeebe:UserTask');
|
142
|
-
}
|
1
|
+
const { is } = require('bpmnlint-utils');
|
2
|
+
|
3
|
+
const {
|
4
|
+
findExtensionElement,
|
5
|
+
findExtensionElements,
|
6
|
+
findParent,
|
7
|
+
hasProperties,
|
8
|
+
hasProperty
|
9
|
+
} = require('../utils/element');
|
10
|
+
|
11
|
+
const { reportErrors } = require('../utils/reporter');
|
12
|
+
|
13
|
+
const { skipInNonExecutableProcess } = require('../utils/rule');
|
14
|
+
|
15
|
+
const { greaterOrEqual } = require('../utils/version');
|
16
|
+
|
17
|
+
const formIdAllowedVersions = {
|
18
|
+
desktop: '8.4',
|
19
|
+
web: '8.0'
|
20
|
+
};
|
21
|
+
|
22
|
+
const ZEEBE_USER_TASK_VERSION = '8.5';
|
23
|
+
|
24
|
+
module.exports = skipInNonExecutableProcess(function({ modeler = 'desktop', version }) {
|
25
|
+
function check(node, reporter) {
|
26
|
+
if (!is(node, 'bpmn:UserTask')) {
|
27
|
+
return;
|
28
|
+
}
|
29
|
+
|
30
|
+
const formDefinition = findExtensionElement(node, 'zeebe:FormDefinition');
|
31
|
+
|
32
|
+
if (!formDefinition) {
|
33
|
+
return;
|
34
|
+
}
|
35
|
+
|
36
|
+
// handle Zebee User Task
|
37
|
+
if (isZeebeUserTask(node)) {
|
38
|
+
|
39
|
+
// handled by no-zeebe-user-task rule
|
40
|
+
if (!greaterOrEqual(version, ZEEBE_USER_TASK_VERSION)) {
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
|
44
|
+
const errors = hasProperty(formDefinition, [
|
45
|
+
'externalReference',
|
46
|
+
'formId'
|
47
|
+
], node) || [];
|
48
|
+
|
49
|
+
if (errors.length) {
|
50
|
+
reportErrors(node, reporter, errors);
|
51
|
+
}
|
52
|
+
|
53
|
+
return;
|
54
|
+
}
|
55
|
+
|
56
|
+
let errors;
|
57
|
+
|
58
|
+
const formIdAllowedVersion = formIdAllowedVersions[ modeler ];
|
59
|
+
|
60
|
+
if (isFormIdAllowed(version, formIdAllowedVersion)) {
|
61
|
+
errors = hasProperty(formDefinition, [
|
62
|
+
'formKey',
|
63
|
+
'formId'
|
64
|
+
], node);
|
65
|
+
} else {
|
66
|
+
errors = hasProperties(formDefinition, {
|
67
|
+
formId: {
|
68
|
+
allowed: false,
|
69
|
+
allowedVersion: formIdAllowedVersion
|
70
|
+
}
|
71
|
+
}, node);
|
72
|
+
|
73
|
+
if (errors.length) {
|
74
|
+
reportErrors(node, reporter, errors);
|
75
|
+
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
|
79
|
+
errors = hasProperties(formDefinition, {
|
80
|
+
formKey: {
|
81
|
+
required: true
|
82
|
+
}
|
83
|
+
}, node);
|
84
|
+
}
|
85
|
+
|
86
|
+
if (errors.length) {
|
87
|
+
reportErrors(node, reporter, errors);
|
88
|
+
|
89
|
+
return;
|
90
|
+
}
|
91
|
+
|
92
|
+
const formKey = formDefinition.get('formKey');
|
93
|
+
|
94
|
+
const userTaskForm = findUserTaskForm(node, formKey);
|
95
|
+
|
96
|
+
if (!userTaskForm) {
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
|
100
|
+
errors = hasProperties(userTaskForm, {
|
101
|
+
body: {
|
102
|
+
required: true
|
103
|
+
}
|
104
|
+
}, node);
|
105
|
+
|
106
|
+
if (errors.length) {
|
107
|
+
reportErrors(node, reporter, errors);
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
return {
|
112
|
+
check
|
113
|
+
};
|
114
|
+
});
|
115
|
+
|
116
|
+
// helpers //////////
|
117
|
+
|
118
|
+
function findUserTaskForm(node, formKey) {
|
119
|
+
const process = findParent(node, 'bpmn:Process');
|
120
|
+
|
121
|
+
if (!process) {
|
122
|
+
return;
|
123
|
+
}
|
124
|
+
|
125
|
+
const userTaskForms = findExtensionElements(process, 'zeebe:UserTaskForm');
|
126
|
+
|
127
|
+
if (userTaskForms && userTaskForms.length) {
|
128
|
+
return userTaskForms.find(userTaskForm => {
|
129
|
+
const id = userTaskForm.get('id');
|
130
|
+
|
131
|
+
return `camunda-forms:bpmn:${ id }` === formKey;
|
132
|
+
});
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
function isFormIdAllowed(version, formIdAllowedVersion) {
|
137
|
+
return greaterOrEqual(version, formIdAllowedVersion);
|
138
|
+
}
|
139
|
+
|
140
|
+
function isZeebeUserTask(node) {
|
141
|
+
return !!findExtensionElement(node, 'zeebe:UserTask');
|
142
|
+
}
|
@@ -1,46 +1,46 @@
|
|
1
|
-
const {
|
2
|
-
is
|
3
|
-
} = require('bpmnlint-utils');
|
4
|
-
|
5
|
-
const {
|
6
|
-
getEventDefinition,
|
7
|
-
hasProperties
|
8
|
-
} = require('../utils/element');
|
9
|
-
|
10
|
-
const { reportErrors } = require('../utils/reporter');
|
11
|
-
|
12
|
-
const { skipInNonExecutableProcess } = require('../utils/rule');
|
13
|
-
|
14
|
-
module.exports = skipInNonExecutableProcess(waitForCompletion);
|
15
|
-
|
16
|
-
/**
|
17
|
-
* Make sure that wait for completion is NOT set to false.
|
18
|
-
*/
|
19
|
-
function waitForCompletion() {
|
20
|
-
function check(node, reporter) {
|
21
|
-
if (!is(node, 'bpmn:ThrowEvent')) {
|
22
|
-
return;
|
23
|
-
}
|
24
|
-
|
25
|
-
const eventDefinition = getEventDefinition(node);
|
26
|
-
|
27
|
-
if (!eventDefinition || !is(eventDefinition, 'bpmn:CompensateEventDefinition')) {
|
28
|
-
return;
|
29
|
-
}
|
30
|
-
|
31
|
-
const errors = hasProperties(eventDefinition, {
|
32
|
-
waitForCompletion: {
|
33
|
-
value: true
|
34
|
-
}
|
35
|
-
}, node);
|
36
|
-
|
37
|
-
if (errors && errors.length) {
|
38
|
-
reportErrors(node, reporter, errors);
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
|
43
|
-
return {
|
44
|
-
check
|
45
|
-
};
|
46
|
-
}
|
1
|
+
const {
|
2
|
+
is
|
3
|
+
} = require('bpmnlint-utils');
|
4
|
+
|
5
|
+
const {
|
6
|
+
getEventDefinition,
|
7
|
+
hasProperties
|
8
|
+
} = require('../utils/element');
|
9
|
+
|
10
|
+
const { reportErrors } = require('../utils/reporter');
|
11
|
+
|
12
|
+
const { skipInNonExecutableProcess } = require('../utils/rule');
|
13
|
+
|
14
|
+
module.exports = skipInNonExecutableProcess(waitForCompletion);
|
15
|
+
|
16
|
+
/**
|
17
|
+
* Make sure that wait for completion is NOT set to false.
|
18
|
+
*/
|
19
|
+
function waitForCompletion() {
|
20
|
+
function check(node, reporter) {
|
21
|
+
if (!is(node, 'bpmn:ThrowEvent')) {
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
|
25
|
+
const eventDefinition = getEventDefinition(node);
|
26
|
+
|
27
|
+
if (!eventDefinition || !is(eventDefinition, 'bpmn:CompensateEventDefinition')) {
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
|
31
|
+
const errors = hasProperties(eventDefinition, {
|
32
|
+
waitForCompletion: {
|
33
|
+
value: true
|
34
|
+
}
|
35
|
+
}, node);
|
36
|
+
|
37
|
+
if (errors && errors.length) {
|
38
|
+
reportErrors(node, reporter, errors);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
return {
|
44
|
+
check
|
45
|
+
};
|
46
|
+
}
|
@@ -1,20 +1,20 @@
|
|
1
|
-
const { is } = require('bpmnlint-utils');
|
2
|
-
|
3
|
-
const { skipInNonExecutableProcess } = require('../utils/rule');
|
4
|
-
|
5
|
-
module.exports = skipInNonExecutableProcess(function() {
|
6
|
-
function check(node, reporter) {
|
7
|
-
|
8
|
-
if (!is(node, 'bpmn:Process')) {
|
9
|
-
return;
|
10
|
-
}
|
11
|
-
|
12
|
-
if (!node.get('camunda:historyTimeToLive')) {
|
13
|
-
reporter.report(node.id, 'Property <historyTimeToLive> should be configured on <bpmn:Process> or engine level.', [ 'historyTimeToLive' ]);
|
14
|
-
}
|
15
|
-
}
|
16
|
-
|
17
|
-
return {
|
18
|
-
check
|
19
|
-
};
|
1
|
+
const { is } = require('bpmnlint-utils');
|
2
|
+
|
3
|
+
const { skipInNonExecutableProcess } = require('../utils/rule');
|
4
|
+
|
5
|
+
module.exports = skipInNonExecutableProcess(function() {
|
6
|
+
function check(node, reporter) {
|
7
|
+
|
8
|
+
if (!is(node, 'bpmn:Process')) {
|
9
|
+
return;
|
10
|
+
}
|
11
|
+
|
12
|
+
if (!node.get('camunda:historyTimeToLive')) {
|
13
|
+
reporter.report(node.id, 'Property <historyTimeToLive> should be configured on <bpmn:Process> or engine level.', [ 'historyTimeToLive' ]);
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
return {
|
18
|
+
check
|
19
|
+
};
|
20
20
|
});
|
package/rules/utils/cron.js
CHANGED
@@ -1,95 +1,95 @@
|
|
1
|
-
const MACROS = [
|
2
|
-
'@yearly',
|
3
|
-
'@annually',
|
4
|
-
'@monthly',
|
5
|
-
'@weekly',
|
6
|
-
'@daily',
|
7
|
-
'@midnight',
|
8
|
-
'@hourly'
|
9
|
-
];
|
10
|
-
const MACRO_REGEX = new RegExp(`^(${ MACROS.join('|') })$`);
|
11
|
-
|
12
|
-
function validateMacro(text) {
|
13
|
-
return MACRO_REGEX.test(text);
|
14
|
-
}
|
15
|
-
|
16
|
-
const ASTERISK = '\\*';
|
17
|
-
const QUESTION_MARK = '\\?';
|
18
|
-
const COMMA = ',';
|
19
|
-
|
20
|
-
const SECOND = '([0-5]?[0-9])';
|
21
|
-
const MINUTE = '([0-5]?[0-9])';
|
22
|
-
const HOUR = '([01]?[0-9]|2[0-3])';
|
23
|
-
const DAY_OF_MONTH = or(`L(-${ or('[0-2]?[0-9]', '3[0-1]') })?`, '[0-2]?[0-9]', '3[0-1]', dayOfMonthSuffix(or('L', '[0-2]?[0-9]', '3[0-1]')));
|
24
|
-
|
25
|
-
const MONTHS = [
|
26
|
-
'JAN',
|
27
|
-
'FEB',
|
28
|
-
'MAR',
|
29
|
-
'APR',
|
30
|
-
'MAY',
|
31
|
-
'JUN',
|
32
|
-
'JUL',
|
33
|
-
'AUG',
|
34
|
-
'SEP',
|
35
|
-
'OCT',
|
36
|
-
'NOV',
|
37
|
-
'DEC'
|
38
|
-
];
|
39
|
-
const MONTH = or('[0]?[1-9]', '1[0-2]', ...MONTHS);
|
40
|
-
|
41
|
-
const WEEK_DAYS = [
|
42
|
-
'MON',
|
43
|
-
'TUE',
|
44
|
-
'WED',
|
45
|
-
'THU',
|
46
|
-
'FRI',
|
47
|
-
'SAT',
|
48
|
-
'SUN'
|
49
|
-
];
|
50
|
-
const DAY_OF_WEEK = or('[0-7]', ...WEEK_DAYS);
|
51
|
-
|
52
|
-
const SECOND_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(SECOND), optionalRange(SECOND))));
|
53
|
-
const MINUTE_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(MINUTE), optionalRange(MINUTE))));
|
54
|
-
const HOUR_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(HOUR), optionalRange(HOUR))));
|
55
|
-
const DAY_OF_MONTH_REGEX = or(ASTERISK, QUESTION_MARK, commaSeparated(optionalRange(DAY_OF_MONTH)));
|
56
|
-
const MONTH_REGEX = or(ASTERISK, commaSeparated(optionalRange(MONTH)));
|
57
|
-
const DAY_OF_WEEK_REGEX = or(ASTERISK, QUESTION_MARK, commaSeparated(or(optionalRange(DAY_OF_WEEK), dayOfWeekSuffix(DAY_OF_WEEK))));
|
58
|
-
|
59
|
-
const CRON_REGEX = new RegExp(`^${ SECOND_REGEX } ${ MINUTE_REGEX } ${ HOUR_REGEX } ${ DAY_OF_MONTH_REGEX } ${ MONTH_REGEX } ${ DAY_OF_WEEK_REGEX }$`, 'i');
|
60
|
-
|
61
|
-
function validateCron(value) {
|
62
|
-
return CRON_REGEX.test(value);
|
63
|
-
}
|
64
|
-
|
65
|
-
module.exports.validateCronExpression = function(value) {
|
66
|
-
return validateMacro(value) || validateCron(value);
|
67
|
-
};
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
// helper //////////////
|
72
|
-
|
73
|
-
function or(...patterns) {
|
74
|
-
return `(${patterns.join('|')})`;
|
75
|
-
}
|
76
|
-
|
77
|
-
function optionalRange(pattern) {
|
78
|
-
return `${pattern}(-${pattern})?`;
|
79
|
-
}
|
80
|
-
|
81
|
-
function commaSeparated(pattern) {
|
82
|
-
return `${pattern}(${COMMA}${pattern})*`;
|
83
|
-
}
|
84
|
-
|
85
|
-
function interval(pattern) {
|
86
|
-
return `${pattern}/\\d+`;
|
87
|
-
}
|
88
|
-
|
89
|
-
function dayOfMonthSuffix(pattern) {
|
90
|
-
return `${pattern}W`;
|
91
|
-
}
|
92
|
-
|
93
|
-
function dayOfWeekSuffix(pattern) {
|
94
|
-
return `${pattern}(#[1-5]|L)`;
|
95
|
-
}
|
1
|
+
const MACROS = [
|
2
|
+
'@yearly',
|
3
|
+
'@annually',
|
4
|
+
'@monthly',
|
5
|
+
'@weekly',
|
6
|
+
'@daily',
|
7
|
+
'@midnight',
|
8
|
+
'@hourly'
|
9
|
+
];
|
10
|
+
const MACRO_REGEX = new RegExp(`^(${ MACROS.join('|') })$`);
|
11
|
+
|
12
|
+
function validateMacro(text) {
|
13
|
+
return MACRO_REGEX.test(text);
|
14
|
+
}
|
15
|
+
|
16
|
+
const ASTERISK = '\\*';
|
17
|
+
const QUESTION_MARK = '\\?';
|
18
|
+
const COMMA = ',';
|
19
|
+
|
20
|
+
const SECOND = '([0-5]?[0-9])';
|
21
|
+
const MINUTE = '([0-5]?[0-9])';
|
22
|
+
const HOUR = '([01]?[0-9]|2[0-3])';
|
23
|
+
const DAY_OF_MONTH = or(`L(-${ or('[0-2]?[0-9]', '3[0-1]') })?`, '[0-2]?[0-9]', '3[0-1]', dayOfMonthSuffix(or('L', '[0-2]?[0-9]', '3[0-1]')));
|
24
|
+
|
25
|
+
const MONTHS = [
|
26
|
+
'JAN',
|
27
|
+
'FEB',
|
28
|
+
'MAR',
|
29
|
+
'APR',
|
30
|
+
'MAY',
|
31
|
+
'JUN',
|
32
|
+
'JUL',
|
33
|
+
'AUG',
|
34
|
+
'SEP',
|
35
|
+
'OCT',
|
36
|
+
'NOV',
|
37
|
+
'DEC'
|
38
|
+
];
|
39
|
+
const MONTH = or('[0]?[1-9]', '1[0-2]', ...MONTHS);
|
40
|
+
|
41
|
+
const WEEK_DAYS = [
|
42
|
+
'MON',
|
43
|
+
'TUE',
|
44
|
+
'WED',
|
45
|
+
'THU',
|
46
|
+
'FRI',
|
47
|
+
'SAT',
|
48
|
+
'SUN'
|
49
|
+
];
|
50
|
+
const DAY_OF_WEEK = or('[0-7]', ...WEEK_DAYS);
|
51
|
+
|
52
|
+
const SECOND_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(SECOND), optionalRange(SECOND))));
|
53
|
+
const MINUTE_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(MINUTE), optionalRange(MINUTE))));
|
54
|
+
const HOUR_REGEX = or(ASTERISK, interval(ASTERISK), commaSeparated(or(interval(HOUR), optionalRange(HOUR))));
|
55
|
+
const DAY_OF_MONTH_REGEX = or(ASTERISK, QUESTION_MARK, commaSeparated(optionalRange(DAY_OF_MONTH)));
|
56
|
+
const MONTH_REGEX = or(ASTERISK, commaSeparated(optionalRange(MONTH)));
|
57
|
+
const DAY_OF_WEEK_REGEX = or(ASTERISK, QUESTION_MARK, commaSeparated(or(optionalRange(DAY_OF_WEEK), dayOfWeekSuffix(DAY_OF_WEEK))));
|
58
|
+
|
59
|
+
const CRON_REGEX = new RegExp(`^${ SECOND_REGEX } ${ MINUTE_REGEX } ${ HOUR_REGEX } ${ DAY_OF_MONTH_REGEX } ${ MONTH_REGEX } ${ DAY_OF_WEEK_REGEX }$`, 'i');
|
60
|
+
|
61
|
+
function validateCron(value) {
|
62
|
+
return CRON_REGEX.test(value);
|
63
|
+
}
|
64
|
+
|
65
|
+
module.exports.validateCronExpression = function(value) {
|
66
|
+
return validateMacro(value) || validateCron(value);
|
67
|
+
};
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
// helper //////////////
|
72
|
+
|
73
|
+
function or(...patterns) {
|
74
|
+
return `(${patterns.join('|')})`;
|
75
|
+
}
|
76
|
+
|
77
|
+
function optionalRange(pattern) {
|
78
|
+
return `${pattern}(-${pattern})?`;
|
79
|
+
}
|
80
|
+
|
81
|
+
function commaSeparated(pattern) {
|
82
|
+
return `${pattern}(${COMMA}${pattern})*`;
|
83
|
+
}
|
84
|
+
|
85
|
+
function interval(pattern) {
|
86
|
+
return `${pattern}/\\d+`;
|
87
|
+
}
|
88
|
+
|
89
|
+
function dayOfMonthSuffix(pattern) {
|
90
|
+
return `${pattern}W`;
|
91
|
+
}
|
92
|
+
|
93
|
+
function dayOfWeekSuffix(pattern) {
|
94
|
+
return `${pattern}(#[1-5]|L)`;
|
95
|
+
}
|