bpmnlint 11.15.0 → 12.0.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/bin/bpmnlint.js +11 -416
- package/lib/linter.js +2 -542
- package/lib/product.js +6 -0
- package/lib/resolver/helper.js +1 -12
- package/lib/resolver/node-resolver.js +6 -135
- package/lib/resolver/static-resolver.js +1 -48
- package/lib/support/compile-config.js +5 -200
- package/lib/test-rule.js +1 -107
- package/lib/testers/helper.js +14 -70
- package/lib/testers/rule-tester.d.ts +10 -28
- package/lib/testers/rule-tester.js +10 -140
- package/lib/traverse.js +1 -56
- package/lib/types.d.ts +20 -81
- package/package.json +43 -10
package/bin/bpmnlint.js
CHANGED
|
@@ -1,47 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { readFile } = require('node:fs/promises');
|
|
4
|
-
const { writeFileSync, existsSync } = require('node:fs');
|
|
5
|
-
const { resolve: resolvePath } = require('node:path');
|
|
6
|
-
|
|
7
|
-
const mri = require('mri');
|
|
8
|
-
const colors = require('ansi-colors');
|
|
9
|
-
|
|
10
|
-
colors.enabled = require('color-support').hasBasic;
|
|
11
|
-
|
|
12
|
-
const {
|
|
13
|
-
red,
|
|
14
|
-
yellow,
|
|
15
|
-
underline,
|
|
16
|
-
bold,
|
|
17
|
-
magenta
|
|
18
|
-
} = colors;
|
|
19
|
-
|
|
20
|
-
const tinyGlob = require('tiny-glob');
|
|
21
|
-
|
|
22
3
|
// @ts-expect-error 'missing <bpmn-moddle> types'
|
|
23
4
|
const { BpmnModdle } = require('bpmn-moddle');
|
|
24
5
|
|
|
25
|
-
const
|
|
26
|
-
const NodeResolver = require('../lib/resolver/node-resolver');
|
|
27
|
-
const { createScopedRequire } = require('../lib/resolver/helper');
|
|
28
|
-
|
|
29
|
-
const Table = require('cli-table');
|
|
30
|
-
|
|
31
|
-
const pluralize = require('pluralize');
|
|
32
|
-
|
|
33
|
-
const { pathStringify } = require('@bpmn-io/moddle-utils');
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @typedef { import('../lib/types').ModdleElement } ModdleElement
|
|
37
|
-
* @typedef { Error & { element: ModdleElement } } BPMNImportWarning
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
const CONFIG_NAME = '.bpmnlintrc';
|
|
41
|
-
|
|
42
|
-
const DEFAULT_CONFIG_CONTENTS = `{
|
|
43
|
-
"extends": "bpmnlint:recommended"
|
|
44
|
-
}`;
|
|
6
|
+
const { createCli } = require('@bpmn-io/lint-core/lib/cli');
|
|
45
7
|
|
|
46
8
|
const HELP_STRING = `
|
|
47
9
|
Usage
|
|
@@ -59,380 +21,13 @@ Usage
|
|
|
59
21
|
|
|
60
22
|
`;
|
|
61
23
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return Promise.all(
|
|
73
|
-
files.map(
|
|
74
|
-
file => tinyGlob(file, { dot: true })
|
|
75
|
-
)
|
|
76
|
-
).then(files => [].concat(...files));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Reads XML form path and return moddle object
|
|
81
|
-
*
|
|
82
|
-
* @param {string} diagramXML
|
|
83
|
-
* @param {BpmnModdle.BPMNModdle} moddle
|
|
84
|
-
*
|
|
85
|
-
* @return { Promise<{ moddleElement: any; warnings: Error[], error?: Error }> } parseResult
|
|
86
|
-
*/
|
|
87
|
-
async function parseDiagram(diagramXML, moddle) {
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const {
|
|
91
|
-
rootElement: moddleElement,
|
|
92
|
-
warnings = []
|
|
93
|
-
} = await moddle.fromXML(diagramXML);
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
moddleElement,
|
|
97
|
-
warnings
|
|
98
|
-
};
|
|
99
|
-
} catch (error) {
|
|
100
|
-
|
|
101
|
-
const {
|
|
102
|
-
|
|
103
|
-
// @ts-expect-error
|
|
104
|
-
warnings = []
|
|
105
|
-
} = error;
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
|
|
109
|
-
// @ts-expect-error
|
|
110
|
-
error,
|
|
111
|
-
warnings
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const categoryMap = {
|
|
117
|
-
warn: 'warning'
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Logs a formatted message
|
|
122
|
-
*/
|
|
123
|
-
function tableEntry(report) {
|
|
124
|
-
let {
|
|
125
|
-
category,
|
|
126
|
-
id = '',
|
|
127
|
-
message,
|
|
128
|
-
name = '',
|
|
129
|
-
path
|
|
130
|
-
} = report;
|
|
131
|
-
|
|
132
|
-
if (path) {
|
|
133
|
-
id = `${ id }#${ pathStringify(path) }`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const color = category === 'error' ? red : yellow;
|
|
137
|
-
|
|
138
|
-
return [ id, color(categoryMap[ category ] || category), message, name ];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function createTable() {
|
|
142
|
-
return new Table({
|
|
143
|
-
chars: {
|
|
144
|
-
'top': '',
|
|
145
|
-
'top-mid': '',
|
|
146
|
-
'top-left': '',
|
|
147
|
-
'top-right': '',
|
|
148
|
-
'bottom': '',
|
|
149
|
-
'bottom-mid': '',
|
|
150
|
-
'bottom-left': '',
|
|
151
|
-
'bottom-right': '',
|
|
152
|
-
'left': ' ',
|
|
153
|
-
'left-mid': '',
|
|
154
|
-
'mid': '',
|
|
155
|
-
'mid-mid': '',
|
|
156
|
-
'right': '',
|
|
157
|
-
'right-mid': '',
|
|
158
|
-
'middle': ' '
|
|
159
|
-
},
|
|
160
|
-
style: {
|
|
161
|
-
'padding-left': 0,
|
|
162
|
-
'padding-right': 0
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function errorAndExit(...args) {
|
|
168
|
-
console.error(...args);
|
|
169
|
-
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function showVersionAndExit() {
|
|
174
|
-
console.log(require('../package.json').version);
|
|
175
|
-
|
|
176
|
-
process.exit(0);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function infoAndExit(...args) {
|
|
180
|
-
console.log(...args);
|
|
181
|
-
|
|
182
|
-
process.exit(0);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Prints lint results to the console
|
|
187
|
-
*
|
|
188
|
-
* @param {String} filePath
|
|
189
|
-
* @param {Object} results
|
|
190
|
-
*/
|
|
191
|
-
function printReports(filePath, results) {
|
|
192
|
-
|
|
193
|
-
let errorCount = 0;
|
|
194
|
-
let warningCount = 0;
|
|
195
|
-
|
|
196
|
-
const table = createTable();
|
|
197
|
-
|
|
198
|
-
Object.entries(results).forEach(function([ name, reports ]) {
|
|
199
|
-
|
|
200
|
-
reports.forEach(function(report) {
|
|
201
|
-
|
|
202
|
-
const {
|
|
203
|
-
category,
|
|
204
|
-
id,
|
|
205
|
-
message,
|
|
206
|
-
name: reportName
|
|
207
|
-
} = report;
|
|
208
|
-
|
|
209
|
-
table.push(tableEntry({
|
|
210
|
-
category,
|
|
211
|
-
id,
|
|
212
|
-
message,
|
|
213
|
-
name: reportName || name
|
|
214
|
-
}));
|
|
215
|
-
|
|
216
|
-
if (category === 'error') {
|
|
217
|
-
errorCount++;
|
|
218
|
-
} else {
|
|
219
|
-
warningCount++;
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const problemCount = warningCount + errorCount;
|
|
225
|
-
|
|
226
|
-
if (problemCount) {
|
|
227
|
-
console.log();
|
|
228
|
-
console.log(underline(resolvePath(filePath)));
|
|
229
|
-
console.log(table.toString());
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
errorCount,
|
|
234
|
-
warningCount
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
async function lintDiagram(diagramPath, config, moddle) {
|
|
239
|
-
|
|
240
|
-
let diagramXML;
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
diagramXML = await readFile(resolvePath(diagramPath), 'utf-8');
|
|
244
|
-
} catch (error) {
|
|
245
|
-
throw errorAndExit(`Error: Failed to read ${diagramPath}\n\n%s`, /** @type { Error } */ (error).message);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const {
|
|
250
|
-
error: importError,
|
|
251
|
-
warnings: importWarnings,
|
|
252
|
-
moddleElement
|
|
253
|
-
} = await parseDiagram(diagramXML, moddle);
|
|
254
|
-
|
|
255
|
-
if (importError) {
|
|
256
|
-
return printReports(diagramPath, {
|
|
257
|
-
'': [
|
|
258
|
-
{
|
|
259
|
-
message: 'Parse error: ' + importError.message,
|
|
260
|
-
category: 'error'
|
|
261
|
-
}
|
|
262
|
-
]
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const importReports = importWarnings.length ? {
|
|
267
|
-
'': importWarnings.map(function(warning) {
|
|
268
|
-
|
|
269
|
-
const {
|
|
270
|
-
element,
|
|
271
|
-
message
|
|
272
|
-
} = /** @type { BPMNImportWarning } */ (warning);
|
|
273
|
-
|
|
274
|
-
const id = element && element.id;
|
|
275
|
-
|
|
276
|
-
return {
|
|
277
|
-
id,
|
|
278
|
-
message: 'Import warning: ' + message.split(/\n/)[0],
|
|
279
|
-
category: 'error'
|
|
280
|
-
};
|
|
281
|
-
})
|
|
282
|
-
} : {};
|
|
283
|
-
|
|
284
|
-
try {
|
|
285
|
-
const linter = new Linter({
|
|
286
|
-
config,
|
|
287
|
-
resolver: new NodeResolver()
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const lintReports = await linter.lint(moddleElement);
|
|
291
|
-
|
|
292
|
-
const allResults = {
|
|
293
|
-
...importReports,
|
|
294
|
-
...lintReports
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
return printReports(diagramPath, allResults);
|
|
298
|
-
} catch (e) {
|
|
299
|
-
throw errorAndExit(e);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async function lint(files, config, maxWarnings) {
|
|
304
|
-
|
|
305
|
-
let errorCount = 0;
|
|
306
|
-
let warningCount = 0;
|
|
307
|
-
|
|
308
|
-
console.log();
|
|
309
|
-
|
|
310
|
-
const moddle = createModdle(config);
|
|
311
|
-
|
|
312
|
-
for (let i = 0; i < files.length; i++) {
|
|
313
|
-
let results = await lintDiagram(files[i], config, moddle);
|
|
314
|
-
|
|
315
|
-
errorCount += results.errorCount;
|
|
316
|
-
warningCount += results.warningCount;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const problemCount = errorCount + warningCount;
|
|
320
|
-
|
|
321
|
-
if (problemCount) {
|
|
322
|
-
|
|
323
|
-
const color = warningCount ? boldYellow : boldRed;
|
|
324
|
-
|
|
325
|
-
console.log();
|
|
326
|
-
console.log(color(
|
|
327
|
-
`✖ ${problemCount} ${pluralize('problem', problemCount)} (${errorCount} ${pluralize('error', errorCount)}, ${warningCount} ${pluralize('warning', warningCount)})`
|
|
328
|
-
));
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (errorCount || (maxWarnings !== -1 && warningCount > maxWarnings)) {
|
|
332
|
-
process.exit(1);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async function run() {
|
|
338
|
-
|
|
339
|
-
const {
|
|
340
|
-
help,
|
|
341
|
-
init,
|
|
342
|
-
version,
|
|
343
|
-
config: configOverridePath,
|
|
344
|
-
'max-warnings': maxWarnings,
|
|
345
|
-
_: files
|
|
346
|
-
} = mri(process.argv.slice(2), {
|
|
347
|
-
string: [ 'config' ],
|
|
348
|
-
alias: {
|
|
349
|
-
c: 'config'
|
|
350
|
-
},
|
|
351
|
-
default: {
|
|
352
|
-
'max-warnings': -1
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
if (version) {
|
|
357
|
-
return showVersionAndExit();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (help) {
|
|
361
|
-
return infoAndExit(HELP_STRING);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (init) {
|
|
365
|
-
if (existsSync(CONFIG_NAME)) {
|
|
366
|
-
throw errorAndExit('Not overriding existing .bpmnlintrc');
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
writeFileSync(CONFIG_NAME, DEFAULT_CONFIG_CONTENTS, 'utf8');
|
|
370
|
-
|
|
371
|
-
throw infoAndExit(`Created ${magenta(CONFIG_NAME)} file`);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (files.length === 0) {
|
|
375
|
-
throw errorAndExit('Error: bpmn file path missing');
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const configPath = configOverridePath || CONFIG_NAME;
|
|
379
|
-
|
|
380
|
-
let configString, config;
|
|
381
|
-
|
|
382
|
-
try {
|
|
383
|
-
configString = await readFile(configPath, 'utf-8');
|
|
384
|
-
} catch (error) {
|
|
385
|
-
|
|
386
|
-
const message = (
|
|
387
|
-
configOverridePath
|
|
388
|
-
? `Error: Could not read ${ magenta(configOverridePath) }`
|
|
389
|
-
: `Error: Could not locate local ${ magenta(CONFIG_NAME) } file. Create one via
|
|
390
|
-
|
|
391
|
-
${magenta('bpmnlint --init')}
|
|
392
|
-
|
|
393
|
-
Learn more about configuring bpmnlint: https://github.com/bpmn-io/bpmnlint#configuration`
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
throw errorAndExit(message);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
config = JSON.parse(configString);
|
|
401
|
-
} catch (error) {
|
|
402
|
-
throw errorAndExit('Error: Could not parse %s\n\n%s', configPath, /** @type { Error } */ (error).message);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const actualFiles = await glob(files);
|
|
406
|
-
|
|
407
|
-
return lint(actualFiles, config, maxWarnings);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
run().catch(errorAndExit);
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Create moddle instance with extensions from the config.
|
|
414
|
-
*
|
|
415
|
-
* @param {object} config
|
|
416
|
-
* @returns {BpmnModdle.BPMNModdle}
|
|
417
|
-
*/
|
|
418
|
-
function createModdle(config) {
|
|
419
|
-
const scopedRequire = createScopedRequire(process.cwd());
|
|
420
|
-
|
|
421
|
-
const options = config.moddleExtensions || {};
|
|
422
|
-
|
|
423
|
-
for (const key in options) {
|
|
424
|
-
const extension = options[key];
|
|
425
|
-
|
|
426
|
-
try {
|
|
427
|
-
options[key] = scopedRequire(extension);
|
|
428
|
-
} catch (error) {
|
|
429
|
-
errorAndExit('Error: Could not load moddle extension <%s> from %s\n\n', key, extension, /** @type { Error } */ (error).message);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
return new BpmnModdle(options);
|
|
435
|
-
} catch (error) {
|
|
436
|
-
errorAndExit('Error: Could not create moddle instance\n\n%s', /** @type { Error } */ (error).message);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
24
|
+
createCli({
|
|
25
|
+
product: require('../lib/product'),
|
|
26
|
+
version: require('../package.json').version,
|
|
27
|
+
Linter: require('../lib/linter'),
|
|
28
|
+
NodeResolver: require('../lib/resolver/node-resolver'),
|
|
29
|
+
createModdle: (extensions) => new BpmnModdle(extensions),
|
|
30
|
+
fileType: 'bpmn',
|
|
31
|
+
help: HELP_STRING,
|
|
32
|
+
docsUrl: 'https://github.com/bpmn-io/bpmnlint#configuration'
|
|
33
|
+
}).run();
|