@sasjs/cli 6.5.0 → 6.7.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.
Files changed (53) hide show
  1. package/README.md +2 -1
  2. package/build/commands/build/build.js +66 -29
  3. package/build/commands/build/spec/build.spec.js +31 -1
  4. package/build/commands/compile/compile.js +168 -33
  5. package/build/commands/compile/compileCommand.js +1 -1
  6. package/build/commands/compile/internal/checkCompileStatus.js +30 -11
  7. package/build/commands/compile/internal/compileFile.js +16 -7
  8. package/build/commands/compile/internal/compileManifest.d.ts +36 -0
  9. package/build/commands/compile/internal/compileManifest.js +610 -0
  10. package/build/commands/compile/internal/index.d.ts +2 -0
  11. package/build/commands/compile/internal/index.js +2 -0
  12. package/build/commands/compile/internal/passThrough.d.ts +12 -0
  13. package/build/commands/compile/internal/passThrough.js +17 -0
  14. package/build/commands/compile/internal/spec/compileTestFile.spec.js +9 -6
  15. package/build/commands/compile/spec/checkCompileStatus.spec.d.ts +1 -0
  16. package/build/commands/compile/spec/checkCompileStatus.spec.js +221 -0
  17. package/build/commands/compile/spec/compileCommand.spec.js +9 -2
  18. package/build/commands/compile/spec/compileManifest.spec.d.ts +1 -0
  19. package/build/commands/compile/spec/compileManifest.spec.js +401 -0
  20. package/build/commands/compile/spec/passThrough.spec.d.ts +1 -0
  21. package/build/commands/compile/spec/passThrough.spec.js +160 -0
  22. package/build/commands/deploy/spec/cbd.spec.js +11 -7
  23. package/build/commands/help/help.js +10 -0
  24. package/build/commands/index.d.ts +1 -0
  25. package/build/commands/index.js +3 -1
  26. package/build/commands/logcheck/collectLogs.d.ts +60 -0
  27. package/build/commands/logcheck/collectLogs.js +386 -0
  28. package/build/commands/logcheck/logCheckConfig.d.ts +72 -0
  29. package/build/commands/logcheck/logCheckConfig.js +283 -0
  30. package/build/commands/logcheck/logcheckCommand.d.ts +6 -0
  31. package/build/commands/logcheck/logcheckCommand.js +200 -0
  32. package/build/commands/logcheck/processLogCheck.d.ts +44 -0
  33. package/build/commands/logcheck/processLogCheck.js +242 -0
  34. package/build/commands/logcheck/renderLogCheck.d.ts +128 -0
  35. package/build/commands/logcheck/renderLogCheck.js +423 -0
  36. package/build/commands/logcheck/spec/collectLogs.spec.d.ts +1 -0
  37. package/build/commands/logcheck/spec/collectLogs.spec.js +477 -0
  38. package/build/commands/logcheck/spec/logCheckConfig.spec.d.ts +1 -0
  39. package/build/commands/logcheck/spec/logCheckConfig.spec.js +323 -0
  40. package/build/commands/logcheck/spec/logcheckCommand.spec.d.ts +1 -0
  41. package/build/commands/logcheck/spec/logcheckCommand.spec.js +360 -0
  42. package/build/commands/logcheck/spec/processLogCheck.spec.d.ts +1 -0
  43. package/build/commands/logcheck/spec/processLogCheck.spec.js +354 -0
  44. package/build/commands/logcheck/spec/renderLogCheck.spec.d.ts +1 -0
  45. package/build/commands/logcheck/spec/renderLogCheck.spec.js +426 -0
  46. package/build/commands/logcheck/types.d.ts +98 -0
  47. package/build/commands/logcheck/types.js +14 -0
  48. package/build/commands/testing/spec/testing.spec.js +8 -2
  49. package/build/types/command/commandAliases.js +1 -0
  50. package/build/types/command/commandFactory.js +2 -0
  51. package/build/types/command/returnCode.d.ts +2 -1
  52. package/build/types/command/returnCode.js +4 -0
  53. package/package.json +3 -3
@@ -0,0 +1,426 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ var renderLogCheck_1 = require("../renderLogCheck");
15
+ var types_1 = require("../types");
16
+ var config = types_1.defaultLogCheckConfig;
17
+ var issue = function (overrides) {
18
+ if (overrides === void 0) { overrides = {}; }
19
+ return (__assign({ line: 11, endLine: 11, column: 4, endColumn: 20, severity: 'error', message: 'Syntax error, expecting one of the following' }, overrides));
20
+ };
21
+ var log = function (overrides) {
22
+ if (overrides === void 0) { overrides = {}; }
23
+ return (__assign({ filePath: '/project/sasjsresults/run.log', displayPath: 'sasjsresults/run.log', issues: [], suppressed: [], counts: { errors: 0, warnings: 0, notes: 0 } }, overrides));
24
+ };
25
+ var summary = function (overrides) {
26
+ if (overrides === void 0) { overrides = {}; }
27
+ return (__assign({ logs: [], skipped: [], missing: [], unreadable: [], usedDefaultInput: false, searched: ['sasjsresults'], totals: {
28
+ errors: 0,
29
+ warnings: 0,
30
+ notes: 0,
31
+ suppressed: 0,
32
+ logs: 0,
33
+ skipped: 0
34
+ } }, overrides));
35
+ };
36
+ var setupLogger = function () {
37
+ var logger = {
38
+ log: jest.fn(),
39
+ table: jest.fn(),
40
+ info: jest.fn(),
41
+ warn: jest.fn(),
42
+ error: jest.fn(),
43
+ success: jest.fn()
44
+ };
45
+ process.logger = logger;
46
+ return {
47
+ logger: logger,
48
+ output: function () { return logger.log.mock.calls.map(function (call) { return call[0]; }).join('\n'); }
49
+ };
50
+ };
51
+ describe('renderHuman', function () {
52
+ afterEach(function () {
53
+ process.logger = undefined;
54
+ });
55
+ it('prints a file table, the issues and the verdict', function () {
56
+ var output = setupLogger().output;
57
+ (0, renderLogCheck_1.renderHuman)(summary({
58
+ logs: [
59
+ log({
60
+ issues: [issue()],
61
+ counts: { errors: 1, warnings: 0, notes: 0 }
62
+ })
63
+ ],
64
+ totals: {
65
+ errors: 1,
66
+ warnings: 0,
67
+ notes: 0,
68
+ suppressed: 0,
69
+ logs: 1,
70
+ skipped: 0
71
+ },
72
+ failure: '1 error'
73
+ }), config);
74
+ var printed = output();
75
+ expect(printed).toContain('SASjs logcheck');
76
+ expect(printed).toContain('sasjsresults/run.log');
77
+ expect(printed).toContain('FAIL');
78
+ expect(printed).toContain('12:5');
79
+ expect(printed).toContain('Syntax error, expecting one of the following');
80
+ expect(printed).toContain('1 error in 1 log');
81
+ expect(printed).toContain('Logcheck failed: 1 error');
82
+ });
83
+ it('says so when the policy is not to fail', function () {
84
+ var output = setupLogger().output;
85
+ (0, renderLogCheck_1.renderHuman)(summary({
86
+ logs: [
87
+ log({
88
+ issues: [issue()],
89
+ counts: { errors: 1, warnings: 0, notes: 0 }
90
+ })
91
+ ],
92
+ totals: {
93
+ errors: 1,
94
+ warnings: 0,
95
+ notes: 0,
96
+ suppressed: 0,
97
+ logs: 1,
98
+ skipped: 0
99
+ }
100
+ }), __assign(__assign({}, config), { failOn: 'never' }));
101
+ expect(output()).toContain('Logcheck passed (fail-on: never)');
102
+ });
103
+ it('reports the suppressed issues as a count, with the codes', function () {
104
+ var output = setupLogger().output;
105
+ (0, renderLogCheck_1.renderHuman)(summary({
106
+ logs: [
107
+ log({
108
+ issues: [issue()],
109
+ suppressed: [issue({ code: '49-169' })],
110
+ counts: { errors: 1, warnings: 0, notes: 0 }
111
+ })
112
+ ],
113
+ totals: {
114
+ errors: 1,
115
+ warnings: 0,
116
+ notes: 0,
117
+ suppressed: 1,
118
+ logs: 1,
119
+ skipped: 0
120
+ }
121
+ }), __assign(__assign({}, config), { ignoreCodes: ['49-169'] }));
122
+ expect(output()).toContain('1 issue suppressed by message code (49-169)');
123
+ });
124
+ it('names a missing path, and hints when it was the default', function () {
125
+ var output = setupLogger().output;
126
+ (0, renderLogCheck_1.renderHuman)(summary({
127
+ missing: ['sasjsresults'],
128
+ usedDefaultInput: true,
129
+ failure: '1 path not found'
130
+ }), config);
131
+ var printed = output();
132
+ expect(printed).toContain('Path not found: sasjsresults');
133
+ expect(printed).toContain('nothing has run yet, or pass a path');
134
+ expect(printed).not.toContain('No logs found at the path searched.');
135
+ });
136
+ it('reports a folder it could not read, and why', function () {
137
+ var output = setupLogger().output;
138
+ (0, renderLogCheck_1.renderHuman)(summary({
139
+ unreadable: [
140
+ {
141
+ displayPath: 'sasjsresults/locked',
142
+ reason: 'could not be listed (EACCES)'
143
+ }
144
+ ],
145
+ failure: '1 folder could not be read'
146
+ }), config);
147
+ var printed = output();
148
+ expect(printed).toContain('Could not read: sasjsresults/locked');
149
+ expect(printed).toContain('could not be listed (EACCES)');
150
+ expect(printed).toContain('Logcheck failed: 1 folder could not be read');
151
+ });
152
+ it('says when a folder holds nothing to check', function () {
153
+ var output = setupLogger().output;
154
+ (0, renderLogCheck_1.renderHuman)(summary(), config);
155
+ expect(output()).toContain('No logs found at the path searched.');
156
+ });
157
+ it('counts the skipped files, and lists them only when asked', function () {
158
+ var output = setupLogger().output;
159
+ var withSkips = summary({
160
+ logs: [log({ counts: { errors: 0, warnings: 0, notes: 0 } })],
161
+ skipped: [
162
+ { displayPath: 'sasjsresults/build.log', reason: 'not a SAS log' }
163
+ ],
164
+ totals: {
165
+ errors: 0,
166
+ warnings: 0,
167
+ notes: 0,
168
+ suppressed: 0,
169
+ logs: 1,
170
+ skipped: 1
171
+ }
172
+ });
173
+ (0, renderLogCheck_1.renderHuman)(withSkips, config);
174
+ expect(output()).toContain('1 file skipped');
175
+ expect(output()).not.toContain('SKIPPED FILE');
176
+ var verbose = setupLogger();
177
+ (0, renderLogCheck_1.renderHuman)(withSkips, config, true);
178
+ expect(verbose.output()).toContain('SKIPPED FILE');
179
+ expect(verbose.output()).toContain('sasjsresults/build.log');
180
+ });
181
+ });
182
+ describe('fileFails', function () {
183
+ it('fails a log carrying an error under the default policy', function () {
184
+ expect((0, renderLogCheck_1.fileFails)({ errors: 1, warnings: 0, notes: 0 }, config)).toBe(true);
185
+ expect((0, renderLogCheck_1.fileFails)({ errors: 0, warnings: 4, notes: 4 }, config)).toBe(false);
186
+ });
187
+ it('follows the policy for warnings and notes', function () {
188
+ var warning = { errors: 0, warnings: 1, notes: 0 };
189
+ var note = { errors: 0, warnings: 0, notes: 1 };
190
+ expect((0, renderLogCheck_1.fileFails)(warning, __assign(__assign({}, config), { failOn: 'warning' }))).toBe(true);
191
+ expect((0, renderLogCheck_1.fileFails)(warning, __assign(__assign({}, config), { failOn: 'note' }))).toBe(true);
192
+ expect((0, renderLogCheck_1.fileFails)(note, __assign(__assign({}, config), { failOn: 'warning' }))).toBe(false);
193
+ expect((0, renderLogCheck_1.fileFails)(note, __assign(__assign({}, config), { failOn: 'note' }))).toBe(true);
194
+ });
195
+ it('fails nothing under the never policy', function () {
196
+ expect((0, renderLogCheck_1.fileFails)({ errors: 9, warnings: 9, notes: 9 }, __assign(__assign({}, config), { failOn: 'never' }))).toBe(false);
197
+ });
198
+ it('does not apply the run-wide warnings budget to one log', function () {
199
+ expect((0, renderLogCheck_1.fileFails)({ errors: 0, warnings: 3, notes: 0 }, __assign(__assign({}, config), { maxWarnings: 1 }))).toBe(false);
200
+ });
201
+ });
202
+ describe('columns', function () {
203
+ it('pads each column to the widest cell and underlines the head', function () {
204
+ var lines = (0, renderLogCheck_1.columns)([
205
+ ['a.log', '3'],
206
+ ['much-longer-name.log', '12']
207
+ ], ['FILE', 'ERRORS']);
208
+ expect(lines).toEqual([
209
+ 'FILE ERRORS',
210
+ '-------------------- ------',
211
+ 'a.log 3',
212
+ 'much-longer-name.log 12'
213
+ ]);
214
+ });
215
+ it('measures colour codes as no width at all', function () {
216
+ var coloured = '\u001b[31mred\u001b[39m';
217
+ var lines = (0, renderLogCheck_1.columns)([[coloured]], ['HEADER']);
218
+ expect(lines[1]).toEqual('------');
219
+ });
220
+ it('indents every line it returns', function () {
221
+ var lines = (0, renderLogCheck_1.columns)([['a']], ['A'], ' ', ' ');
222
+ expect(lines.every(function (line) { return line.startsWith(' '); })).toBe(true);
223
+ });
224
+ });
225
+ describe('truncate', function () {
226
+ it('leaves a short message alone', function () {
227
+ expect((0, renderLogCheck_1.truncate)('Syntax error')).toEqual('Syntax error');
228
+ });
229
+ it('collapses the whitespace SAS wrapped', function () {
230
+ expect((0, renderLogCheck_1.truncate)('a message\n wrapped by SAS')).toEqual('a message wrapped by SAS');
231
+ });
232
+ it('cuts a long message on a word boundary', function () {
233
+ var message = 'word '.repeat(40).trim();
234
+ var cut = (0, renderLogCheck_1.truncate)(message);
235
+ expect(cut.length).toBeLessThanOrEqual(110);
236
+ expect(cut.endsWith('...')).toBe(true);
237
+ expect(cut).not.toContain(' ');
238
+ expect(message.startsWith(cut.replace('...', '').trim())).toBe(true);
239
+ });
240
+ });
241
+ describe('severityLabel', function () {
242
+ it('states the severity in words', function () {
243
+ expect((0, renderLogCheck_1.severityLabel)('error')).toEqual('error');
244
+ expect((0, renderLogCheck_1.severityLabel)('warning')).toEqual('warning');
245
+ expect((0, renderLogCheck_1.severityLabel)('note')).toEqual('note');
246
+ });
247
+ });
248
+ describe('lineLabel', function () {
249
+ it('is one-based, and names the range a message was wrapped over', function () {
250
+ expect((0, renderLogCheck_1.lineLabel)(issue())).toEqual('12:5');
251
+ expect((0, renderLogCheck_1.lineLabel)(issue({ endLine: 14 }))).toEqual('12-15:5');
252
+ });
253
+ });
254
+ describe('summarise', function () {
255
+ it('states what was found and over how many logs', function () {
256
+ expect((0, renderLogCheck_1.summarise)(summary({
257
+ totals: {
258
+ errors: 2,
259
+ warnings: 1,
260
+ notes: 0,
261
+ suppressed: 0,
262
+ logs: 2,
263
+ skipped: 0
264
+ }
265
+ }), 2)).toEqual('2 errors, 1 warning in 2 logs');
266
+ });
267
+ it('says so when there is nothing to report', function () {
268
+ expect((0, renderLogCheck_1.summarise)(summary({
269
+ totals: {
270
+ errors: 0,
271
+ warnings: 0,
272
+ notes: 0,
273
+ suppressed: 0,
274
+ logs: 1,
275
+ skipped: 0
276
+ }
277
+ }), 1)).toEqual('No issues in 1 log');
278
+ });
279
+ it('names the files it read out of the files it saw', function () {
280
+ expect((0, renderLogCheck_1.summarise)(summary({
281
+ totals: {
282
+ errors: 1,
283
+ warnings: 0,
284
+ notes: 0,
285
+ suppressed: 0,
286
+ logs: 1,
287
+ skipped: 2
288
+ }
289
+ }), 1)).toEqual('1 error in 1 of 3 files');
290
+ });
291
+ });
292
+ describe('toJson', function () {
293
+ it('states line and column from one', function () {
294
+ var json = (0, renderLogCheck_1.toJson)(summary({
295
+ logs: [
296
+ log({
297
+ issues: [issue({ code: '22-322' })],
298
+ counts: { errors: 1, warnings: 0, notes: 0 }
299
+ })
300
+ ]
301
+ }), config);
302
+ expect(json.logs[0].issues[0]).toEqual({
303
+ line: 12,
304
+ endLine: 12,
305
+ column: 5,
306
+ endColumn: 21,
307
+ severity: 'error',
308
+ code: '22-322',
309
+ message: 'Syntax error, expecting one of the following'
310
+ });
311
+ });
312
+ it('leaves out a code an issue does not carry', function () {
313
+ var json = (0, renderLogCheck_1.toJson)(summary({ logs: [log({ issues: [issue()] })] }), config);
314
+ expect(json.logs[0].issues[0]).not.toHaveProperty('code');
315
+ });
316
+ it('lists a folder it could not read', function () {
317
+ var json = (0, renderLogCheck_1.toJson)(summary({
318
+ unreadable: [
319
+ { displayPath: 'logs/locked', reason: 'could not be listed' }
320
+ ]
321
+ }), config);
322
+ expect(json.unreadable).toEqual([
323
+ { folder: 'logs/locked', reason: 'could not be listed' }
324
+ ]);
325
+ });
326
+ it('carries the counts, the policy and the verdict', function () {
327
+ var json = (0, renderLogCheck_1.toJson)(summary({
328
+ logs: [
329
+ log({
330
+ issues: [issue()],
331
+ counts: { errors: 1, warnings: 0, notes: 0 },
332
+ skippedReason: undefined
333
+ })
334
+ ],
335
+ skipped: [{ displayPath: 'build.log', reason: 'not a SAS log' }],
336
+ totals: {
337
+ errors: 1,
338
+ warnings: 0,
339
+ notes: 0,
340
+ suppressed: 0,
341
+ logs: 1,
342
+ skipped: 1
343
+ },
344
+ failure: '1 error'
345
+ }), __assign(__assign({}, config), { maxWarnings: 2 }));
346
+ expect(json.summary).toEqual({
347
+ logs: 1,
348
+ skipped: 1,
349
+ errors: 1,
350
+ warnings: 0,
351
+ notes: 0,
352
+ suppressed: 0,
353
+ failed: true,
354
+ failure: '1 error'
355
+ });
356
+ expect(json.config.maxWarnings).toEqual(2);
357
+ expect(json.skipped).toEqual([
358
+ { file: 'build.log', reason: 'not a SAS log' }
359
+ ]);
360
+ });
361
+ });
362
+ describe('toAnnotations', function () {
363
+ it('writes one workflow command per issue, with one-based positions', function () {
364
+ var annotations = (0, renderLogCheck_1.toAnnotations)(summary({
365
+ logs: [
366
+ log({
367
+ issues: [
368
+ issue({ code: '22-322' }),
369
+ issue({ severity: 'warning', message: 'Symbol not resolved' }),
370
+ issue({ severity: 'note', message: 'Division by zero' })
371
+ ]
372
+ })
373
+ ]
374
+ }));
375
+ expect(annotations).toEqual([
376
+ '::error file=sasjsresults/run.log,line=12,col=5,endLine=12,endColumn=21,title=SAS ERROR 22-322::Syntax error, expecting one of the following',
377
+ '::warning file=sasjsresults/run.log,line=12,col=5,endLine=12,endColumn=21,title=SAS WARNING::Symbol not resolved',
378
+ '::notice file=sasjsresults/run.log,line=12,col=5,endLine=12,endColumn=21,title=SAS NOTE::Division by zero'
379
+ ]);
380
+ });
381
+ it('escapes what a workflow command reads as syntax', function () {
382
+ var annotations = (0, renderLogCheck_1.toAnnotations)(summary({
383
+ logs: [
384
+ log({
385
+ displayPath: 'logs/with,comma.log',
386
+ issues: [issue({ message: '100% wrong\nsecond line' })]
387
+ })
388
+ ]
389
+ }));
390
+ expect(annotations[0]).toContain('file=logs/with%2Ccomma.log');
391
+ expect(annotations[0]).toContain('::100%25 wrong%0Asecond line');
392
+ });
393
+ });
394
+ describe('renderLogCheck', function () {
395
+ afterEach(function () {
396
+ jest.restoreAllMocks();
397
+ process.logger = undefined;
398
+ });
399
+ it('writes JSON to stdout, and nothing through the logger', function () {
400
+ var logger = setupLogger().logger;
401
+ var write = jest
402
+ .spyOn(process.stdout, 'write')
403
+ .mockImplementation(function () { return true; });
404
+ (0, renderLogCheck_1.renderLogCheck)(summary(), config, { format: 'json' });
405
+ expect(write).toHaveBeenCalled();
406
+ expect(JSON.parse(String(write.mock.calls[0][0])).tool).toEqual('sasjs logcheck');
407
+ expect(logger.log).not.toHaveBeenCalled();
408
+ });
409
+ it('writes annotations to stdout, and nothing when there are none', function () {
410
+ var write = jest
411
+ .spyOn(process.stdout, 'write')
412
+ .mockImplementation(function () { return true; });
413
+ (0, renderLogCheck_1.renderLogCheck)(summary({ logs: [log({ issues: [issue()] })] }), config, {
414
+ format: 'annotations'
415
+ });
416
+ expect(String(write.mock.calls[0][0])).toContain('::error file=');
417
+ write.mockClear();
418
+ (0, renderLogCheck_1.renderLogCheck)(summary(), config, { format: 'annotations' });
419
+ expect(write).not.toHaveBeenCalled();
420
+ });
421
+ it('prints the human report by default', function () {
422
+ var output = setupLogger().output;
423
+ (0, renderLogCheck_1.renderLogCheck)(summary(), config);
424
+ expect(output()).toContain('SASjs logcheck');
425
+ });
426
+ });
@@ -0,0 +1,98 @@
1
+ import { LogIssue } from '@sasjs/utils/logs';
2
+ /**
3
+ * When a log check fails. `error` is the default: an ERROR is SAS refusing to
4
+ * do something, and a gate red on day one gets switched off, so the notable
5
+ * notes legacy code produces in bulk are reported but do not fail the run.
6
+ */
7
+ export type FailOn = 'error' | 'warning' | 'note' | 'never';
8
+ /** How the result is presented. Only `human` carries colour. */
9
+ export type OutputFormat = 'human' | 'json' | 'annotations';
10
+ /**
11
+ * The policy for a log check: what fails it, and what is suppressed. The
12
+ * defaults are set here, a `logCheck` section in `sasjsconfig.json` may
13
+ * override them, and the flags on the command line override both.
14
+ */
15
+ export interface LogCheckConfig {
16
+ /**
17
+ * Lowest severity that fails the check - see {@link FailOn}.
18
+ *
19
+ * `never` does not disable `maxWarnings`: a budget is a specific
20
+ * instruction and still fails the run when it is exceeded.
21
+ */
22
+ failOn: FailOn;
23
+ /**
24
+ * Number of warnings tolerated before the check fails, whatever `failOn`
25
+ * says. Undefined means no budget, which is not the same as zero.
26
+ */
27
+ maxWarnings?: number;
28
+ /**
29
+ * SAS message codes to suppress, e.g. `49-169` for the note about a
30
+ * quoted string being truncated. The code is the stable identifier SAS
31
+ * gives a message, which is what makes it the right thing to key on.
32
+ */
33
+ ignoreCodes: string[];
34
+ /**
35
+ * Log paths to leave out, as a plain substring (`sasjsresults/legacy`) or a
36
+ * glob (`**&#47;build*.log`). Matched against the path shown in the report.
37
+ */
38
+ ignorePaths: string[];
39
+ /**
40
+ * Folder to check when no path is given on the command line. Resolved
41
+ * against the project folder.
42
+ */
43
+ folder?: string;
44
+ }
45
+ export declare const defaultLogCheckConfig: LogCheckConfig;
46
+ /** Counts of the issues a log carries, after suppression. */
47
+ export interface IssueCounts {
48
+ errors: number;
49
+ warnings: number;
50
+ notes: number;
51
+ }
52
+ export declare const emptyCounts: () => IssueCounts;
53
+ /**
54
+ * One log as the check found it. A log that could not be read, is too large,
55
+ * or is not a SAS log carries a `skippedReason` and no issues - it is still
56
+ * part of the report, because a file the user asked about that was quietly
57
+ * dropped is worse than one reported as skipped.
58
+ */
59
+ export interface ScannedLog {
60
+ /** Absolute path, as it was read. */
61
+ filePath: string;
62
+ /** Path as shown to the user, relative to the project folder where possible. */
63
+ displayPath: string;
64
+ /** Issues that survive the suppression rules. */
65
+ issues: LogIssue[];
66
+ /** Issues removed by `ignoreCodes`, reported only as a count. */
67
+ suppressed: LogIssue[];
68
+ counts: IssueCounts;
69
+ /** Set when the file was not checked at all. */
70
+ skippedReason?: string;
71
+ }
72
+ /** A file the check did not look inside, with the reason it did not. */
73
+ export interface SkippedFile {
74
+ displayPath: string;
75
+ reason: string;
76
+ }
77
+ /** Everything one run of the check produced, before any rendering. */
78
+ export interface LogCheckSummary {
79
+ logs: ScannedLog[];
80
+ skipped: SkippedFile[];
81
+ /** Paths the user named that do not exist. */
82
+ missing: string[];
83
+ /** Folders the check could not list, so their logs were never looked at. */
84
+ unreadable: SkippedFile[];
85
+ /** Whether the paths were defaulted rather than named by the user. */
86
+ usedDefaultInput: boolean;
87
+ /** Folders and files that were searched, as shown in the report. */
88
+ searched: string[];
89
+ totals: IssueCounts & {
90
+ suppressed: number;
91
+ /** Logs actually parsed. */
92
+ logs: number;
93
+ /** Files that were passed over, for whatever reason. */
94
+ skipped: number;
95
+ };
96
+ /** Why the check failed, or undefined when it passed. */
97
+ failure?: string;
98
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emptyCounts = exports.defaultLogCheckConfig = void 0;
4
+ exports.defaultLogCheckConfig = {
5
+ failOn: 'error',
6
+ ignoreCodes: [],
7
+ ignorePaths: []
8
+ };
9
+ var emptyCounts = function () { return ({
10
+ errors: 0,
11
+ warnings: 0,
12
+ notes: 0
13
+ }); };
14
+ exports.emptyCounts = emptyCounts;
@@ -116,8 +116,11 @@ describe('sasjs test', function () {
116
116
  return [4 /*yield*/, copyTestFiles(target.name)];
117
117
  case 2:
118
118
  _a.sent();
119
- return [4 /*yield*/, (0, __1.build)(target)];
119
+ return [4 /*yield*/, (0, __1.compile)(target)];
120
120
  case 3:
121
+ _a.sent();
122
+ return [4 /*yield*/, (0, __1.build)(target)];
123
+ case 4:
121
124
  _a.sent();
122
125
  process.logger = new utils_1.Logger(utils_1.LogLevel.Off);
123
126
  return [2 /*return*/];
@@ -443,8 +446,11 @@ describe('sasjs test', function () {
443
446
  return [4 /*yield*/, copyTestFiles(target.name)];
444
447
  case 2:
445
448
  _a.sent();
446
- return [4 /*yield*/, (0, __1.build)(target)];
449
+ return [4 /*yield*/, (0, __1.compile)(target)];
447
450
  case 3:
451
+ _a.sent();
452
+ return [4 /*yield*/, (0, __1.build)(target)];
453
+ case 4:
448
454
  _a.sent();
449
455
  process.logger = new utils_1.Logger(utils_1.LogLevel.Off);
450
456
  return [2 /*return*/];
@@ -11,6 +11,7 @@ exports.aliasMap = new Map([
11
11
  ['deploy', ['d']],
12
12
  ['doc', ['docs']],
13
13
  ['help', ['h']],
14
+ ['logcheck', ['lc']],
14
15
  ['request', ['rq']],
15
16
  ['run', ['r']],
16
17
  ['test', ['t']],
@@ -24,6 +24,7 @@ var requestCommand_1 = require("../../commands/request/requestCommand");
24
24
  var helpCommand_1 = require("../../commands/help/helpCommand");
25
25
  var jobCommand_1 = require("../../commands/job/jobCommand");
26
26
  var lintCommand_1 = require("../../commands/lint/lintCommand");
27
+ var logcheckCommand_1 = require("../../commands/logcheck/logcheckCommand");
27
28
  var flowCommand_1 = require("../../commands/flow/flowCommand");
28
29
  var fsCommand_1 = require("../../commands/fs/fsCommand");
29
30
  var snippetsCommand_1 = require("../../commands/snippets/snippetsCommand");
@@ -49,6 +50,7 @@ exports.commandFactory = new Map([
49
50
  ['init', function (args) { return new initCommand_1.InitCommand(args); }],
50
51
  ['job', function (args) { return new jobCommand_1.JobCommand(args); }],
51
52
  ['lint', function (args) { return new lintCommand_1.LintCommand(args); }],
53
+ ['logcheck', function (args) { return new logcheckCommand_1.LogCheckCommand(args); }],
52
54
  ['flow', function (args) { return new flowCommand_1.FlowCommand(args); }],
53
55
  ['request', function (args) { return new requestCommand_1.RequestCommand(args); }],
54
56
  ['run', function (args) { return new runCommand_1.RunCommand(args); }],
@@ -3,5 +3,6 @@ export declare enum ReturnCode {
3
3
  InvalidCommand = 1,
4
4
  InternalError = 2,
5
5
  LintError = 3,
6
- AlreadyInitialised = 4
6
+ AlreadyInitialised = 4,
7
+ LogCheckError = 5
7
8
  }
@@ -11,4 +11,8 @@ var ReturnCode;
11
11
  // InvalidCommand, which also covers unparseable arguments, so a script can
12
12
  // tell a refusal from a bad invocation.
13
13
  ReturnCode[ReturnCode["AlreadyInitialised"] = 4] = "AlreadyInitialised";
14
+ // `logcheck` found issues at or above the configured failure policy. Kept
15
+ // apart from LintError so a pipeline can tell a code-quality failure from a
16
+ // run that SAS complained about.
17
+ ReturnCode[ReturnCode["LogCheckError"] = 5] = "LogCheckError";
14
18
  })(ReturnCode || (exports.ReturnCode = ReturnCode = {}));
package/package.json CHANGED
@@ -70,10 +70,10 @@
70
70
  "registry": "https://registry.npmjs.org"
71
71
  },
72
72
  "dependencies": {
73
- "@sasjs/adapter": "^5.1.1",
73
+ "@sasjs/adapter": "^5.1.3",
74
74
  "@sasjs/core": "^6.1.1",
75
75
  "@sasjs/lint": "4.9.0",
76
- "@sasjs/utils": "^4.1.2",
76
+ "@sasjs/utils": "^4.3.2",
77
77
  "axios": "1.20.0",
78
78
  "chalk": "4.1.2",
79
79
  "dotenv": "18.0.8",
@@ -123,5 +123,5 @@
123
123
  "ws": "8.21.0",
124
124
  "nanoid": "3.3.18"
125
125
  },
126
- "version": "6.5.0"
126
+ "version": "6.7.0"
127
127
  }